简体   繁体   English

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

[英]What kind of memory leaks do lambda event handlers cause?

Event handlers can easily lead to memory leaks, because the event's invocation list holds a reference to the event handling instance, so that the event handling instance cannot be garbage collected, if the event source is still alive.事件处理程序很容易导致内存泄漏,因为事件的调用列表持有对事件处理实例的引用,因此如果事件源还活着,则无法对事件处理实例进行垃圾回收。

But consider the following code:但请考虑以下代码:

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

My assumptions/questions why the different event handling versions may cause a memory leak:我的假设/问题为什么不同的事件处理版本可能会导致内存泄漏:

  • Version 1: Because the invocation target clearly references the instance of MyClass .版本 1:因为调用目标明确引用了MyClass的实例。
  • Version 2: Because the reference to localHandler implies a reference to the instance of MyClass - except, maybe, if the code inside localHandler has no references to the instance of MyClass ?版本 2:因为对localHandler的引用意味着对MyClass实例的引用 - 除了,如果localHandler内部的代码没有对MyClass实例的引用?
  • Version 3: Because the lambda contains a closure, which itself is a reference to the instance of MyClass - or is it?版本 3:因为 lambda 包含一个闭包,它本身是对MyClass实例的引用 - 还是它?
  • Version 4: Because the lambda does not reference the instance of MyClass , this may not cause a leak?版本 4:因为 lambda 没有引用MyClass的实例,这可能不会导致泄漏?

And, follow-up questions to versions 3 and 4:并且,对版本 3 和 4 的后续问题:

  • Where is the "magic helper object" that .Net creates for the lambda/closure stored and does it (always) include a reference that would keep the instance of MyClass alive? .Net 为存储的 lambda/闭包创建的“魔法助手对象”在哪里,它是否(总是)包含一个可以保持MyClass实例存活的引用?
  • If lambda event handlers can leak, they should only be used in scenarios where this is not a problem (eg MyClass instances outlive SomeClass instances), because they cannot be removed using -= ?如果 lambda 事件处理程序可能会泄漏,则它们应该仅用于这不是问题的场景(例如MyClass实例比SomeClass实例存活时间更长),因为它们不能使用-=删除?

EDIT: This post (with the original title "When can event handlers cause memory leaks?") was suggested as a duplicate, of Why and How to avoid Event Handler memory leaks?编辑:这篇文章(原标题为“什么时候事件处理程序会导致内存泄漏?”)被建议作为为什么和如何避免事件处理程序内存泄漏的副本 , but I disagree, because the question was directed at lambda event handlers specifically. ,但我不同意,因为这个问题专门针对 lambda 事件处理程序。 I rephrased the question/title to make this more clear.我改写了问题/标题以使其更清楚。

Disclaimer : I can't guarantee this is 100% truth - your question is quite deep and I could make a mistake.免责声明:我不能保证这是 100% 真实的 - 你的问题很深,我可能会犯错误。

However, I hope it will give you some thoughts or directions.但是,我希望它能给你一些想法或方向。

Let's consider this question according CLR memory organization:让我们根据CLR内存组织来考虑这个问题:

Local method variables and method parameters are stored in the method stack frame in memory (except they declared with ref keyword).局部方法变量和方法参数存储在内存中的方法栈帧中(除非它们用ref关键字声明)。

Stack stores value types and reference-type variables references which point on objects in heap.堆栈存储指向堆中对象的值类型和引用类型变量引用。

Method stack frame is exists while method execution, and local method variables will dissappear with stack frame after method ended.方法执行时方法栈帧存在,方法结束后局部方法变量随栈帧消失。

Except if local variables were captured one way or another, it also relates to compiler work, you can read about it at Jon Skeet's website:除了以一种或另一种方式捕获局部变量外,它还与编译器工作有关,您可以在 Jon Skeet 的网站上阅读它:

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

Version 1 : OnSomeEvent method is member of MyClass and it will captured by Someclass source instance, until delegates that refers on this method will not be removed from event.版本 1OnSomeEvent方法是MyClass成员,它将被Someclass source实例捕获,直到引用此方法的委托不会从事件中删除。 So, MyClass instance that was created in constructor, placed in heap and holds this method will not be collected by GC until its method reference will not be removed from event.因此,在构造函数中创建、放置在堆中并持有此方法的MyClass实例将不会被GC收集,直到其方法引用不会从事件中删除。

Compiler compiles lambda by specific way, please read Implementation example paragraph fully:编译器通过特定方式编译 lambda,请完整阅读实现示例段落:

https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#anonymous-function-conversions https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#anonymous-function-conversions

Version 4 : 2 links I provided give a kick lambda will be compiled to MyClass method, which will be captured by SomeClass instance as in Version 1版本 4 :我提供的 2 个链接将被编译为MyClass方法,该方法将被SomeClass实例捕获,如版本 1 所示

Version 2 : I don't know nuances about how local methods will be compiled, but it should be same as in Version 4 (and, therefore, Version 1 ).第 2 版:我不知道如何编译本地方法的细微差别,但它应该与第 4 版(因此是第 1 版)中的相同。

Version 3 : All local variables will be captured by interesting way.版本 3 :所有局部变量都将以有趣的方式捕获。

You have 'object x' also, so compiler generated class will be created, which will contain public field public object x;您也有“对象 x”,因此将创建编译器生成的类,该类将包含公共字段public object x; and method which will be translated from your lambda (see Implementation example paragraph).和方法将从您的 lambda 转换而来(请参阅实现示例段落)。

So, I think in Versions 1,2,4 will be internally the same: MyClass will contain the method which will be used as event handler.所以,我认为版本 1、2、4在内部是相同的:MyClass 将包含将用作事件处理程序的方法。

In Version 3 compiler generated class will be created and it will hold your local variable and method translated from lamdba.版本 3编译器生成的类将被创建,它将保存从 lamdba 翻译的本地变量和方法。

Any instance of any class will not be collected by GC until SomeClass event has its method in invocation list.SomeClass事件在调用列表中有其方法之前,GC 不会收集任何类的任何实例。

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

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