繁体   English   中英

lambda 事件处理程序会导致什么样的内存泄漏?

[英]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) 
    {
        //...
    }
}

我的假设/问题为什么不同的事件处理版本可能会导致内存泄漏:

  • 版本 1:因为调用目标明确引用了MyClass的实例。
  • 版本 2:因为对localHandler的引用意味着对MyClass实例的引用 - 除了,如果localHandler内部的代码没有对MyClass实例的引用?
  • 版本 3:因为 lambda 包含一个闭包,它本身是对MyClass实例的引用 - 还是它?
  • 版本 4:因为 lambda 没有引用MyClass的实例,这可能不会导致泄漏?

并且,对版本 3 和 4 的后续问题:

  • .Net 为存储的 lambda/闭包创建的“魔法助手对象”在哪里,它是否(总是)包含一个可以保持MyClass实例存活的引用?
  • 如果 lambda 事件处理程序可能会泄漏,则它们应该仅用于这不是问题的场景(例如MyClass实例比SomeClass实例存活时间更长),因为它们不能使用-=删除?

编辑:这篇文章(原标题为“什么时候事件处理程序会导致内存泄漏?”)被建议作为为什么和如何避免事件处理程序内存泄漏的副本 ,但我不同意,因为这个问题专门针对 lambda 事件处理程序。 我改写了问题/标题以使其更清楚。

免责声明:我不能保证这是 100% 真实的 - 你的问题很深,我可能会犯错误。

但是,我希望它能给你一些想法或方向。

让我们根据CLR内存组织来考虑这个问题:

局部方法变量和方法参数存储在内存中的方法栈帧中(除非它们用ref关键字声明)。

堆栈存储指向堆中对象的值类型和引用类型变量引用。

方法执行时方法栈帧存在,方法结束后局部方法变量随栈帧消失。

除了以一种或另一种方式捕获局部变量外,它还与编译器工作有关,您可以在 Jon Skeet 的网站上阅读它:

http://jonskeet.uk/csharp/csharp2/delegates.html#captured.variables

版本 1OnSomeEvent方法是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 不会收集任何类的任何实例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM