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>();
public List<Containee> Controls
{
get { return _controls; }
set { _controls = value; }
}
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.Controls.Add(this);
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点击订阅我的文章



哥们,你这 _controls 就根本没有加入任何东西,后面调clear()有什么用处?代码和你所说不一致。
谢谢兄台的指正,写示例代码的时候太急,写漏了。