[英]What kind of memory leaks do lambda event handlers cause?
事件处理程序很容易导致内存泄漏,因为事件的调用列表持有对事件处理实例的引用,因此如果事件源还活着,则无法对事件处理实例进行垃圾回收。
但请考虑以下代码:
public class SomeClass
{
public event EventHandler SomeEvent;
}
public class MyClass
{
public MyClass(SomeClass source)
{
//VERSION 1
source.SomeEvent += OnSomeEvent;
//VERSION 2
void localHandler(object s, EventArgs args) { Console.WriteLine("some action with(out) any references"); }
source.SomeEvent += localHandler;
//VERSION 3
var x = new object();
source.SomeEvent += (s, e) => { Console.WriteLine("some event fired, using closure x:" + x.GetHashCode()); };
//VERSION 4
source.SomeEvent += (s, e) => { Console.WriteLine("some action without any references"); };
}
private void OnSomeEvent(object sender, EventArgs e)
{
//...
}
}
我的假设/问题为什么不同的事件处理版本可能会导致内存泄漏:
MyClass
的实例。localHandler
的引用意味着对MyClass
实例的引用 - 除了,如果localHandler
内部的代码没有对MyClass
实例的引用?MyClass
实例的引用 - 还是它?MyClass
的实例,这可能不会导致泄漏?并且,对版本 3 和 4 的后续问题:
MyClass
实例存活的引用?MyClass
实例比SomeClass
实例存活时间更长),因为它们不能使用-=
删除?编辑:这篇文章(原标题为“什么时候事件处理程序会导致内存泄漏?”)被建议作为为什么和如何避免事件处理程序内存泄漏的副本? ,但我不同意,因为这个问题专门针对 lambda 事件处理程序。 我改写了问题/标题以使其更清楚。
免责声明:我不能保证这是 100% 真实的 - 你的问题很深,我可能会犯错误。
但是,我希望它能给你一些想法或方向。
让我们根据CLR
内存组织来考虑这个问题:
局部方法变量和方法参数存储在内存中的方法栈帧中(除非它们用ref
关键字声明)。
堆栈存储指向堆中对象的值类型和引用类型变量引用。
方法执行时方法栈帧存在,方法结束后局部方法变量随栈帧消失。
除了以一种或另一种方式捕获局部变量外,它还与编译器工作有关,您可以在 Jon Skeet 的网站上阅读它:
http://jonskeet.uk/csharp/csharp2/delegates.html#captured.variables
版本 1 : OnSomeEvent
方法是MyClass
成员,它将被Someclass source
实例捕获,直到引用此方法的委托不会从事件中删除。 因此,在构造函数中创建、放置在堆中并持有此方法的MyClass
实例将不会被GC
收集,直到其方法引用不会从事件中删除。
编译器通过特定方式编译 lambda,请完整阅读实现示例段落:
https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#anonymous-function-conversions
版本 4 :我提供的 2 个链接将被编译为MyClass
方法,该方法将被SomeClass
实例捕获,如版本 1 所示
第 2 版:我不知道如何编译本地方法的细微差别,但它应该与第 4 版(因此是第 1 版)中的相同。
版本 3 :所有局部变量都将以有趣的方式捕获。
您也有“对象 x”,因此将创建编译器生成的类,该类将包含公共字段public object x;
和方法将从您的 lambda 转换而来(请参阅实现示例段落)。
所以,我认为版本 1、2、4在内部是相同的:MyClass 将包含将用作事件处理程序的方法。
在版本 3编译器生成的类将被创建,它将保存从 lamdba 翻译的本地变量和方法。
在SomeClass
事件在调用列表中有其方法之前,GC 不会收集任何类的任何实例。
source.SomeEvent = null
来删除订阅。 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events#unsubscribing
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.