为什么这不会导致内存泄漏事件时,不退订(Why this does not cause a memo

2019-07-18 11:29发布

我想了解事件如何导致内存泄漏。 我发现了一个很好的解释在这个计算器的问题,但看着Windg对象时,我收到,结果混淆。 首先,我有一个简单的类,如下所示。

class Person
    {
        public string LastName { get; set; }
        public string FirstName { get; set; }

        public event EventHandler UponWakingUp;
        public Person()  {  }

        public void Wakeup()
        {
            Console.WriteLine("Waking up");
            if (UponWakingUp != null)
                UponWakingUp(null, EventArgs.Empty);
        }
    }

现在我在Windows窗体应用程序如下使用这个类。

public partial class Form1 : Form
    {
        Person John = new Person() { LastName = "Doe", FirstName = "John" };

        public Form1()
        {
            InitializeComponent();

            John.UponWakingUp += new EventHandler(John_UponWakingUp);
        }

        void John_UponWakingUp(object sender, EventArgs e)
        {
            Console.WriteLine("John is waking up");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            John = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            MessageBox.Show("done");
         }
    }

正如你所看到的,我instaniated Person类和订阅UponWakingUp事件。 我有这个表单上的按钮。 当用户点击这个按钮,我这个人实例设置为空,而不取消订阅的事件。 然后,我调用GC.Collect,以确保被执行了Garbade集合。 我在这里显示一个消息框,这样我就可以连接WinDbg通过Form1类看起来引用的帮助和这个类中我没有看到该事件的任何引用(WinDBG的输出如下所示虽然Form1中有太长的数据,我展示有关我的问题)。 这个类有Person类的引用,但它是空的。 基本上,这看起来像内存泄漏给我不为Form1不具有Person类的任何引用甚至thouh它没有退订的事件。

我的问题是,如果这样做导致内存泄漏。 如果不是,为什么不呢?

0:005> !do 0158d334   
Name:        WindowsFormsApplication1.Form1  
MethodTable: 00366390  
EEClass:     00361718  
Size:        332(0x14c) bytes  
File:        c:\Sandbox\\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe  
Fields:  
      MT    Field   Offset                 Type VT     Attr    Value Name  
619af744  40001e0        4        System.Object  0 instance 00000000 __identity  
60fc6c58  40002c3        8 ...ponentModel.ISite  0 instance 00000000 site  
619af744  4001534      b80        System.Object  0   static 0158dad0 EVENT_MAXIMIZEDBOUNDSCHANGED  
**00366b70  4000001      13c ...plication1.Person  0 instance 00000000 John**  
60fc6c10  4000002      140 ...tModel.IContainer  0 instance 00000000 components  
6039aadc  4000003      144 ...dows.Forms.Button  0 instance 015ad06c button1  

0:008> !DumpHeap -mt 00366b70    
 Address       MT     Size  
total 0 objects  
Statistics:  
      MT    Count    TotalSize Class Name  
Total 0 objects  

Answer 1:

这是一个循环引用的情况。 形式具有与监听通过约翰字段中的事件的对象的引用。 反过来,约翰的形式引用时,其UponWakingUp活动是由窗体的构造认购。

循环引用可以是在某些自动存储器管理方案的一个问题,特别是在这样的引用计数。 但是.NET垃圾回收器不会有问题。 只要既不形式对象也不Person对象有任何其他参考文献,这两者之间的循环引用不能保持彼此活着。

有要么在你的代码中没有额外的引用。 这通常会导致两个对象进行垃圾回收。 但Form类是特殊的,只要本机Windows窗口存在,存储在的WinForms保持句柄到对象表的内部参考保持表单对象活着。 这使约翰活着。

所以,正常的方式,这是清理的是,用户通过点击右上角的X关闭窗口。 这反过来又导致本地窗口句柄被销毁。 其去除从内部表的形式引用。 接下来的垃圾收集现在看到的只是循环引用,并收集他们。



Answer 2:

答案其实是在回答你有联系的问题:

当听众附加一个事件侦听器的事件,源对象将获得监听对象的引用。 这意味着, 听者不能被垃圾收集器收集, 直到该事件处理程序被分离,或源对象被收集

您正在释放对象( Person ),因此监听器 (你的Form )是确定要收集这就是为什么没有内存泄漏。

在这种情况下是围绕IE的其他方式,当你要处置将发生内存泄漏Form ,但事件 (你的Person对象)仍然活着持有对它的引用。



文章来源: Why this does not cause a memory leak when event is not unsubscribed