WeakEventManager <teventsource,teventargs =“”>和PropertyChangedEventManager导致内存泄漏</teventsource,>

时间:2014-02-12 09:27:50

标签: c# .net 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;
    }
}

是的,我知道没有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得到了提示。

2 个答案:

答案 0 :(得分:4)

我不认为WeakEventManager<,>的isue特定于非WPF,因为我也可以在WPF应用程序中重现内存泄漏。

问题在于事件表的管理。对于每个订阅,WeakEventManager在表中创建一个条目。此条目和表格(必要时)是强引用的。

问题是,默认情况下,WeakEventManager不会清除记录。你必须致电RemoveHandler。但要注意。这不是线程安全的。如果你从另一个线程调用它可能会失败(不抛出异常,你只会遇到仍然存在内存泄漏)。从终结器调用时,它也不能可靠地工作。

我还研究了源代码,发现虽然它包含了在收到事件时在AddHandler 上进行清理的逻辑,但默认情况下它被禁用(参见{{1 }} =&gt; WeakEventManager.cs)。此外,您无法访问WeakEventTable.CurrentWeakEventTable.IsCleanupEnabled方法,因为执行此操作所需的方法和属性为Cleanupprivate。所以你甚至不能创建子类来访问这些方法/修改行为。

internal已损坏

所以基本上(据我所知) WeakEventManager<,>被设计破坏(它保留了对订阅者表条目的强引用)。 而不是修复MemoryLeak它将仅减少MemoryLeak (事件源和侦听器可以被垃圾收集,但事件订阅的条目不是=&gt;新内存泄漏)。当然WeakEventManager<,>引入的内存泄漏很小。

答案 1 :(得分:3)

根据msdncodeproject的信息,我意识到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 });
            }
        }
    }
}