C#中事件如何导致内存泄漏,弱引用如何帮助缓解这种情况?
有两种方式(我知道)在C#中导致无意的内存泄漏:
- 不处理实施
IDisposable
的资源 - 错误地引用和解引用事件。
我真的不明白第二点。 如果源对象比侦听器的生命周期更长,并且在没有其他引用的情况下侦听器不再需要事件,则使用正常的.NET事件将导致内存泄漏:源对象将侦听器对象保存在内存中应该被垃圾收集。
你能解释一下事件如何导致C#代码中的内存泄漏,以及如何使用弱引用来解决这个问题,而且没有弱引用?
当侦听器将事件侦听器附加到事件时,源对象将获得对侦听器对象的引用。 这意味着监听器不能被垃圾收集器收集,直到事件处理程序被分离,或者收集源对象。
考虑以下类:
class Source { public event EventHandler SomeEvent; } class Listener { public Listener(Source source) { // attach an event listner; this adds a reference to the // source_SomeEvent method in this instance to the invocation list // of SomeEvent in source source.SomeEvent += new EventHandler(source_SomeEvent); } void source_SomeEvent(object sender, EventArgs e) { // whatever } }
…然后下面的代码:
Source newSource = new Source(); Listener listener = new Listener(newSource); listener = null;
即使我们将null
分配给listener
,也不会有资格进行垃圾回收,因为newSource
仍然持有对事件处理程序( Listener.source_SomeEvent
)的引用。 为了解决这种泄漏问题,当事件监听器不再需要的时候,总是要分离事件监听器是非常重要的。
上面的例子是为了关注泄漏问题而写的。 为了解决这个问题,最简单的方法是让Listener
继续引用Source
,以便稍后分离事件监听器:
class Listener { private Source _source; public Listener(Source source) { _source = source; // attach an event listner; this adds a reference to the // source_SomeEvent method in this instance to the invocation list // of SomeEvent in source _source.SomeEvent += source_SomeEvent; } void source_SomeEvent(object sender, EventArgs e) { // whatever } public void Close() { if (_source != null) { // detach event handler _source.SomeEvent -= source_SomeEvent; _source = null; } } }
然后调用代码可以发信号通知使用该对象完成,这将删除Source
必须给“Listener”的引用。
Source newSource = new Source(); Listener listener = new Listener(newSource); // use listener listener.Close(); listener = null;
阅读乔恩Skeet关于事件的优秀文章 。 这并不是真正的“内存泄漏”,而是更多的是没有断开连接的参考。 所以一定要记住-=
一个事件处理程序,你在上一点,你应该是金。
严格来说,托pipe.NET项目的“沙箱”中没有“内存泄漏”; 只有参考文献比开发者认为必要的要长。 弗雷德里克有权利; 当你将一个处理程序附加到事件上时,因为处理程序通常是一个实例方法(需要实例),所以只要维护该引用,包含该侦听程序的类的实例就会保留在内存中。 如果侦听器实例依次包含对其他类的引用(例如,对包含对象的反引用),则在侦听器已经离开其他所有作用域之后,堆可以保持相当长的时间。
也许对Delegate和MulticastDelegate有一些更深奥知识的人可以对此有所了解。 我的看法是,如果以下所有情况都是真的,那么真正的泄漏是可能的:
- 事件监听器需要通过实现IDisposable来释放外部/非托pipe资源,但是要么不这样做
- 事件多播委托不会从其重写的Finalize()方法中调用Dispose()方法
- 包含该事件的类不会通过其自己的IDisposable实现或Finalize()在委托的每个目标上调用Dispose()。
我从来没有听说过有关在委托目标上调用Dispose()的最佳实践,更不用说事件监听器了,所以我只能假定.NET开发人员知道他们在这种情况下做了什么。 如果这是真的,并且事件背后的MulticastDelegate尝试正确地处理侦听器,那么所有必要的是在需要处置的侦听类上正确实现IDisposable。