我有一个应用程序,我无法删除事件处理程序,因为我不知道何时将释放最后一个引用。
我的应用程序包含一个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;
}
}
是的,我知道没有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;
}
}
使用.Net 4.5编译的代码也会耗尽内存。我使用Thread.Sleep构造here得到了提示。
答案 0 :(得分:4)
我不认为WeakEventManager<,>
的isue特定于非WPF,因为我也可以在WPF应用程序中重现内存泄漏。
问题在于事件表的管理。对于每个订阅,WeakEventManager
在表中创建一个条目。此条目和表格(必要时)是强引用的。
问题是,默认情况下,WeakEventManager
不会清除记录。你必须致电RemoveHandler
。但要注意。这不是线程安全的。如果你从另一个线程调用它可能会失败(不抛出异常,你只会遇到仍然存在内存泄漏)。从终结器调用时,它也不能可靠地工作。
我还研究了源代码,发现虽然它包含了在收到事件时在AddHandler
和上进行清理的逻辑,但默认情况下它被禁用(参见{{1 }} =&gt; WeakEventManager.cs
)。此外,您无法访问WeakEventTable.CurrentWeakEventTable.IsCleanupEnabled
方法,因为执行此操作所需的方法和属性为Cleanup
或private
。所以你甚至不能创建子类来访问这些方法/修改行为。
internal
已损坏所以基本上(据我所知) WeakEventManager<,>
被设计破坏(它保留了对订阅者表条目的强引用)。
而不是修复MemoryLeak它将仅减少MemoryLeak (事件源和侦听器可以被垃圾收集,但事件订阅的条目不是=&gt;新内存泄漏)。当然WeakEventManager<,>
引入的内存泄漏很小。
答案 1 :(得分:3)
根据msdn和codeproject的信息,我意识到WeakEventManager(TEventSource, TEventArgs)
类只适用于WPF应用程序。我正在使用WinForms,这就是它似乎无法工作的原因。
我决定创建自己的WeakEventManager
,无需使用.Net框架提供的WeakEventManager
版本。
我的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 });
}
}
}
}