DotNet解决事件(Event)内存泄漏通用方法

DotNet解决事件(Event)内存泄漏通用方法

  一个生命周期较短的对象(对象A)注册到一个生命周期较长(对象B)的某个事件(Event)上,两者便无形之间建立一个引用关系(B引用A)。这种引用关系导致GC在进行垃圾回收的时候不会将A是为垃圾对象,最终使其常驻内存(或者说将A捆绑到B上,具有了和B一样的生命周期)。这种让无用的对象不能被GC垃圾回收的现象,在托管环境下就是一种典型的内存泄漏问题。

代码下载
提取码:xazz

事实上,.NET内存泄漏的最普遍原因是静态变量引用了对象。

造成事件(Event)内存泄漏的原因

事件本质上就是一个System.Delegate对象。
Delegate分解成两个部分:委托的事情和委托的对象。与之相似地,.NET的Delegate对象同样可以分解成两个部分:委托的功能(Method)和目标对象(Target),这可以直接从Delegate的定义就可以看出来:

1
2
3
4
5
6
public abstract class Delegate : ICloneable, ISerializable
{
// Others
public MethodInfo Method { get; }
public object Target { get; }
}

常用的事件处理类型EventHandlerEventHandler<TEventArgs>本质上就是一个Delegate。

他们继承自System.MulticastDelegateMulticastDelegate派生于Delegate

1
2
3
4
5
[Serializable, ComVisible(true)]
public delegate void EventHandler(object sender, EventArgs e);

[Serializable]
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs: EventArgs;

  经过简单一句事件注册代码就通过一个EventHandler(EventHandler)事件的源(TodoListManager)和事件的监听者(TodoListForm)两着关联起来,三者之间的关系如下图所示。从这张图中我们可以看到:TodoListForm实际上是通过注册的EventHandler的Target属性被TodoListManager间接引用着的。所以才会导致TodoListForm在关闭之后,永远不能拿成为垃圾对象,因为TodoListManager是一个基于static属性定义的Singleton对象,永远是GC的根。

image_thumb_2.png

解决方法

  当对象A注册到B的某个事件上,A并不受到B的“强制引用”。既然不能“强引用(Strong Reference)”,那就只能是“弱引用(Weak Reference)”。通过System.WeakReference来解决这个问题。

方法:采取某种机制,让事件源(Event Source)的EventHandler通过WeakReference的方式与事件监听者建立关系。只有在这种情况下,事件监听者没有了事件源的强制引用,在我们不用的时候才能及时成为垃圾对象,等待GC对它的清理。

image_thumb_3.png

  通过传入EventHandler<TEventArgs>对象构造WeakReferenceHandler,在EventHandler的Target属性基础上建立WeakReference对象,在执行处理事件的时候通过该WeakReference找到真正的目标对象,如果找得到则通过反射在其基础上调用相应的方法;反之,如果通过不能得到Target,那么表明该事件的监听对象已经被GC当作垃圾对象回收掉了。为了在注册事件的时候方遍,特定义了一个隐式的类型转换:WeakReferenceHandler转换成EventHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Reflection;
namespace Artech.MemLeakByEvents
{
public class WeakEventHandler<TEventArgs> where TEventArgs : EventArgs
{
public WeakReference Reference
{ get; private set; }

public MethodInfo Method
{ get; private set; }

public EventHandler<TEventArgs> Handler
{ get; private set; }

public WeakEventHandler(EventHandler<TEventArgs> eventHandler)
{
Reference = new WeakReference(eventHandler.Target);
Method = eventHandler.Method;
Handler = Invoke;
}

public void Invoke(object sender, TEventArgs e)
{
object target = Reference.Target;
if (null != target)
{
Method.Invoke(target, new object[] { sender, e });
}
}

/// <summary>
/// 隐式转换
/// </summary>
/// <param name="weakHandler"></param>
public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakHandler)
{
return weakHandler.Handler;
}
}
}

实际进行事件注册:

1
2
3
4
5
private void TodoListForm_Load(object sender, EventArgs e)
{
SynchronizationContext = SynchronizationContext.Current;
TodoListManager.Instance.TodoListChanged += new WeakEventHandler<TodoListEventArgs>(TodoListManager_TodoListChanged);
}

参考:

事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[下篇] (提供Source Code下载)