Seb*_*ann 6 .net c# events memory-leaks event-handling
我有一个应用程序,我无法删除事件处理程序,因为我不知道何时将释放最后一个引用.
我的应用程序包含一个PropertyChanged
事件源,它被放入一个也实现的容器类中INotifyPropertyChanged
.此层次结构包含6个以上的级别.可以将级别的每个实例放置到多个其他实例中.这就是为什么我无法确定何时释放这些实例的原因.
最低级别的实例将适用于整个应用程序运行时.这导致所有其他实例都不会被释放,我得到了内存泄漏.
为了避免这个事件驱动的内存泄漏我试图使用WeakEventManager(TEventSource, TEventArgs)
.此类仅在.Net 4.5中可用,并且由于与现有硬件的兼容性,我将使用.Net 4.0.
在.Net 4.0中PropertyChangedEventManager
可以使用相同的功能INotifyPropertyChanged
.
我的课程正确释放.
但是仍然存在内存泄漏.
我将我的应用程序简化为以下产生内存泄漏的代码:
// This code will force the memory leak
while (true)
{
var eventSource = new StateChangedEventSource();
var eventReceiver = new StateChangedEventReceiver();
PropertyChangedEventManager.AddListener(eventSource, eventReceiver, string.Empty);
}
public class EventSource : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
public class EventReceiver : IWeakEventListener
{
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
是的,我知道没有RemoveListener
电话.我无法确定何时从未使用过实例并且可以释放它.如果我知道我可以使用正常的事件注册和注销.在那种情况下,我不必使用PropertyChangedEventManager
.
我的示例代码有什么问题?为什么会产生内存泄漏?
编辑2014/02/17:
我试过WeakEventManager(TEventSource, TEventArgs)
和.Net 4.5,问题仍然存在.
var eventSource = new EventSource();
var i = 0;
while (true)
{
var eventReceiver = new EventReceiver();
// --> Use only one of the following three lines. Each of them will produce a memory leak.
WeakEventManager<EventSource, PropertyChangedEventArgs>.AddHandler(eventSource, "PropertyChanged", eventReceiver.OnEvent);
PropertyChangedEventManager.AddListener(eventSource, eventReceiver, string.Empty);
WeakEventManager<EventSource, EventArgs>.AddHandler(eventSource, "SomeOtherEvent", eventReceiver.OnSomeOtherEvent);
// <--
++i;
if (i == 1 << 18)
{
Thread.Sleep(10);
GC.Collect(2);
Thread.Sleep(10);
i = 0;
}
}
public class EventSource : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<EventArgs> SomeOtherEvent;
}
public class EventReceiver : IWeakEventListener
{
public void OnSomeOtherEvent(object sender, EventArgs args)
{
}
public void OnEvent(object sender, PropertyChangedEventArgs args)
{
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
使用.Net 4.5编译的代码也会耗尽内存.我在这里使用Thread.Sleep构造得到了提示.
我不认为该问题WeakEventManager<,>
特定于非 WPF,因为我也可以在 WPF 应用程序中重现内存泄漏。
问题出在事件表的管理上。对于每个订阅,都会WeakEventManager
在表中创建一个条目。该条目和表格(必然)是强引用的。
问题是,默认情况下,WeakEventManager
不会清除记录。你必须打电话RemoveHandler
。但要小心。它不是线程安全的。如果您从另一个线程调用它,它可能会失败(不会抛出异常,您只会遇到内存泄漏)。当从终结器调用时,它也不能可靠地工作。
我还调查了源代码,发现虽然它包含在收到事件时进行清理的逻辑AddHandler
,但默认情况下它是禁用的(请参阅WeakEventManager.cs
=> WeakEventTable.CurrentWeakEventTable.IsCleanupEnabled
)。此外,您无法访问该Cleanup
方法,因为执行此操作所需的方法和属性是private
或internal
。因此,您甚至无法创建子类来访问这些方法/修改行为。
WeakEventManager<,>
被打破所以基本上(据我所知)WeakEventManager<,>
被设计破坏了(它保留了对订户表条目的 StrongReference)。它不会修复MemoryLeak,而只会减少MemoryLeak(事件源和监听器可以被垃圾收集,但事件订阅的条目不是=>新的内存泄漏)。当然引入的内存泄漏WeakEventManager<,>
是很小的。
根据msdn和codeproject上的信息,我意识到该类WeakEventManager(TEventSource, TEventArgs)
只能在 WPF 应用程序中工作。我正在使用 WinForms,它似乎不起作用的原因是什么?
我决定创建自己的项目,无需使用.Net 框架提供的WeakEventManager
内置功能即可工作。WeakEventManager
my 的实现WeakEventManager
使用后台线程来清理所有实例。也许有更好的解决方案,但该解决方案将在我的应用程序中正常工作。
public static class ThreadedWeakEventManager
{
private static readonly TimeSpan CleanupInterval = TimeSpan.FromSeconds(1.0);
private static readonly List<IInternalWeakEventManager> EventManagers = new List<IInternalWeakEventManager>();
private static volatile bool _performCleanup = true;
static ThreadedWeakEventManager()
{
new Thread(Cleanup) { IsBackground = true, Priority = ThreadPriority.Lowest }.Start();
}
public static void AddHandler<TEventArgs>(object eventSource, string eventName, EventHandler<TEventArgs> eventHandler)
where TEventArgs : EventArgs
{
var weakEventManager = new InternalWeakEventManager<TEventArgs>(eventSource, eventName, eventHandler);
lock (EventManagers)
{
EventManagers.Add(weakEventManager);
}
}
public static void AddPropertyChangedHandler(INotifyPropertyChanged eventSource, EventHandler<PropertyChangedEventArgs> eventHandler)
{
AddHandler(eventSource, "PropertyChanged", eventHandler);
}
public static void AddCollectionChangedEventHandler(INotifyCollectionChanged eventSource, EventHandler<NotifyCollectionChangedEventArgs> eventHandler)
{
AddHandler(eventSource, "CollectionChanged", eventHandler);
}
public static void RemoveHandler<TEventArgs>(object eventSource, string eventName, EventHandler<TEventArgs> eventHandler)
where TEventArgs : EventArgs
{
if (eventSource == null || string.IsNullOrWhiteSpace(eventName) || eventHandler == null)
{
return;
}
lock (EventManagers)
{
EventManagers.RemoveAll(item => object.ReferenceEquals(item.EventData.EventSource, eventSource) && item.EventName.Equals(eventName) && eventHandler.Method.Equals(item.EventData.EventHandlerMethodInfo));
}
}
public static void RemovePropertyChangedHandler(INotifyPropertyChanged eventSource, EventHandler<PropertyChangedEventArgs> eventHandler)
{
RemoveHandler(eventSource, "PropertyChanged", eventHandler);
}
public static void RemoveCollectionChangedEventHandler(INotifyCollectionChanged eventSource, EventHandler<NotifyCollectionChangedEventArgs> eventHandler)
{
RemoveHandler(eventSource, "CollectionChanged", eventHandler);
}
public static void CancelCleanup()
{
_performCleanup = false;
}
private static void Cleanup()
{
while (_performCleanup)
{
Thread.Sleep(CleanupInterval);
lock (EventManagers)
{
for (var i = EventManagers.Count - 1; i >= 0; --i)
{
var item = EventManagers[i];
if (item.EventData.IsGarbageCollected)
{
item.UnwireEvent();
EventManagers.RemoveAt(i);
}
}
}
}
}
private interface IInternalWeakEventManager
{
string EventName { get; }
IWeakEventData EventData { get; }>
void UnwireEvent();
void OnEvent(object sender, EventArgs args);
}
private class InternalWeakEventManager<TEventArgs> : IInternalWeakEventManager
where TEventArgs : EventArgs
{
private static readonly MethodInfo OnEventMethodInfo = typeof(InternalWeakEventManager<TEventArgs>).GetMethod("OnEvent");
private EventInfo _eventInfo;
private Delegate _onEvent;
public InternalWeakEventManager(object eventSource, string eventName, EventHandler<TEventArgs> eventHandler)
{
this.EventData = new WeakEventData<TEventArgs>(eventSource, eventHandler);
this.WireEvent(eventSource, eventName);
}
public string EventName
{
get { return this._eventInfo.Name; }
}
public IWeakEventData EventData { get; private set; }
public void UnwireEvent()
{
var eventSource = this.EventData.EventSource;
if (eventSource == null)
{
return;
}
this._eventInfo.RemoveEventHandler(eventSource, this._onEvent);
}
public void OnEvent(object sender, EventArgs args)
{
this.EventData.ForwardEvent(sender, args);
}
private void WireEvent(object eventSource, string eventName)
{
this._eventInfo = eventSource.GetType().GetEvents().FirstOrDefault(item => item.Name == eventName);
if (this._eventInfo == null)
{
throw new InvalidOperationException(string.Format("The event source type {0} doesn't contain an event named {1}.", eventSource.GetType().FullName, eventName));
}
this._onEvent = Delegate.CreateDelegate(this._eventInfo.EventHandlerType, this, OnEventMethodInfo);
this._eventInfo.AddEventHandler(eventSource, this._onEvent);
}
}
private interface IWeakEventData
{
bool IsGarbageCollected { get; }
object EventSource { get; }>
MethodInfo EventHandlerMethodInfo { get; }
void ForwardEvent(object sender, EventArgs args);
}
private class WeakEventData<TEventArgs> : IWeakEventData
where TEventArgs : EventArgs
{
private readonly WeakReference _eventSource;
private readonly WeakReference _eventTargetInstance;
public WeakEventData(object eventSource, EventHandler<TEventArgs> eventHandler)
{
this._eventSource = new WeakReference(eventSource);
this._eventTargetInstance = new WeakReference(eventHandler.Target);
this.EventHandlerMethodInfo = eventHandler.Method;
}
public object EventSource
{
get { return this._eventSource.Target; }
}
public MethodInfo EventHandlerMethodInfo { get; private set; }
public bool IsGarbageCollected
{
get
{
return !this._eventSource.IsAlive || !this._eventTargetInstance.IsAlive;
}
}
public void ForwardEvent(object sender, EventArgs args)
{
var target = this._eventTargetInstance.Target;
if (target != null)
{
this.EventHandlerMethodInfo.Invoke(target, new[] { sender, args });
}
}
}
}
Run Code Online (Sandbox Code Playgroud)