DotNet中未退订事件引起的内存泄露问题
今天在InfoQ上看到一篇文章处理.NET中的内存泄露,里头提到说DotNet内存泄露的几种常见情况,
- 使用静态引用
- 未退订的事件-作者认为这是最常见的内存泄漏原因
- 未退订的静态事件
- 未调用Dispose方法
- 使用不彻底的Dispose方法
- 在Windows Forms中对BindingSource的误用
- 未在WorkItem/CAB上调用Remove
当时对于第二点,“未退订事件”引起的内存泄露不是很理解。后来仔细分析了一下,又参考了MSDN上关于事件和委托的解释,终于意识到了自己以前没有注意的一个问题。
假设,类A提供了一个事件,类B和类C均订阅了该事件。如果类B和类C的实例不再使用了,而又没有显式退订此事件,那么类B和类C的实例将不会被GC回收,直至类A实例被销毁为止。
下面的代码说明了这种情况:
public class Container { private List<Containee> _controls = new List<Containee>(); private String _currentState; public event EventHandler StateChanged; public String CurrentState { get { return _currentState; } set { if (value != _currentState) { _currentState = value; if (StateChanged != null) { StateChanged(this, null); } } } } public void ClearControls() { _controls.Clear(); } } public class Containee { private String _name; private Container _container; public Containee(String controlName, Container container) { _name = controlName; _container = container; container.StateChanged += OnStateChanged; } public void OnStateChanged(object sender, EventArgs e) { Container container = sender as Container; if (container != null) { // Do something.... Console.WriteLine("{0}: State change, current state is {1}.", _name, container.CurrentState); } } } class Program { static void Main(string[] args) { Container container = new Container(); new Containee("Control 1", container); new Containee("Control 2", container); new Containee("Control 3", container); container.CurrentState = "State1"; Console.WriteLine("================================================"); // 清除所有子控件 container.ClearControls(); // 添加新控件进来 new Containee("Control 4", container); new Containee("Control 5", container); new Containee("Control 6", container); container.CurrentState = "State2"; } }
运行结果:
如上述结果看到的,尽管旧控件已经被clear掉了,但是它们仍在起作用。
当我们在后面代码中将container容器上的控件清除掉的时候,我们会习惯性的以为这些控件已经不再被引用了,资源会被自动回收掉。而设计Containee的人可能在写事件订阅的时候也不会想起背后的逻辑,习以为常的觉得,只要在代码里找到该Containee实例都被“=”给了谁,然后把它的引用置为null就行了。殊不知,C#的event-delegate模型下,当一个类实例订阅了一个事件的时候,此实例已经被隐式的传给了“事件”了(CLR的事件模型是建立在委托System.Delegate之上的,事件可以看成是一个委托类)。因此,只要Container不销毁,事件也就不销毁,Containee的实例就不会被回收,除非Containee退订了该事件。
自己在写事件处理的时候根本没有考虑过这样的问题,不知道以前写过的代码会不会也存在这样的问题。不管怎么,以后得注意一些了。
你可能对下面的文章感兴趣
本博客遵循CC协议2.5,即署名-非商业性使用-相同方式共享
写作很辛苦,转载请注明~
祝你有个愉快的阅读体验:-D点击订阅我的文章

最新评论
没这个需求,就纯粹是瞎折腾研究罢了。 在家又不需要用到手机上网。
直接买个无线路由就解决了
服,搜了下 貌似网上没说原理的
OK
呵呵,搞错了,域名是http://www.jianfeing .com