为什么这不会导致内存泄漏事件时,不退订退订、这不、内存、事件

2023-09-04 00:55:37 作者:笙语亦凉

我想了解事件如何会导致内存泄漏。我发现了一处很好的解释this计算器的问题,但在看在Windg对象,我感到困惑的结果。首先,我有一个简单的类如下:

 类Person
    {
        公共字符串名字{获得;组; }
        公共字符串名字{获得;组; }

        公共事件的EventHandler UponWakingUp;
        公众人物(){}

        公共无效唤醒()
        {
            Console.WriteLine(醒来);
            如果(UponWakingUp!= NULL)
                UponWakingUp(NULL,EventArgs.Empty);
        }
    }
 

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

 公共部分类Form1中:形态
    {
        人约翰=新的Person(){姓氏=李四,名字=约翰};

        公共Form1中()
        {
            的InitializeComponent();

            John.UponWakingUp + =新的EventHandler(John_UponWakingUp);
        }

        无效John_UponWakingUp(对象发件人,EventArgs的)
        {
            Console.WriteLine(约翰正在苏醒);
        }

        私人无效的button1_Click(对象发件人,EventArgs的)
        {
            约翰= NULL;
            所以GC.Collect();
            GC.WaitForPendingFinalizers();
            所以GC.Collect();
            的MessageBox.show(完成);
         }
    }
 

正如你所看到的,我instaniated Person类和订阅UponWakingUp事件。我对这种形式的按钮。当用户点击这个按钮,我把这个人实例空,而不取消订阅事件。然后,我调用GC.Collect,以确保正在被执行Garbade集合。我显示一个消息框,在这里,这样我可以连接WinDbg通过Form1类看参考资料的帮助和在这个类中我没有看到任何提及该事件(WinDBG的输出如下虽然Form1中有太长的数据,我展示有关我的问题)。这个类有一个引用Person类,但它为空。基本上,这似乎并不像一个内存泄漏给我为Form1不更是把thouh它并没有取消订阅事件的任何引用Person类。

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

  0:005> !做0158d334
名称:WindowsFormsApplication1.Form1
方法表:00366390
EEClass:00361718
尺寸:332(0x14c)字节
文件:C:\沙盒\\ WindowsFormsApplication1 \ WindowsFormsApplication1 \斌\调试\ WindowsFormsApplication1.exe
领域:
      MT字段偏移类型VT的Attr值名称
619af744 40001e0 4 System.Object的0实例00000000 __identity
60fc6c58 40002c3 8 ... ponentModel.ISite 0实例00000000站点
619af744 4001534 B80 System.Object的0静态0158dad0 EVENT_MAXIMIZEDBOUNDSCHANGED
** 00366b70 4000001 13C ... plication1.Person 0实例00000000约翰**
60fc6c10 4000002 140 ...... tModel.IContainer 0实例00000000组件
6039aadc 4000003 144 ...... dows.Forms.Button 0实例015ad06c按钮1

0:008> !DumpHeap -mt 00366b70
 地址MT尺寸
共0对象
统计:
      MT计数TotalSize类名称
共0对象
 
动态内存常见错误

解决方案

这是一个的循环引用的的情况。窗体有一个对监听事件通过的约翰·的领域对象。反过来,约翰引用了表单时其UponWakingUp事件认购形式的构造。

循环引用可以在一定的自动内存管理方案的一个问题,尤其如此引用计数。但在.NET垃圾回收器不会有一个问题。只要未窗体对象,也不是Person对象有任何其他的参考,这两者之间的循环引用跟不上对方还活着。

有要么在code无其他参考资料。这通常会导致两个对象进行垃圾回收。但Form类是特殊的,只要本机Windows窗口存在,存储在一个句柄到对象表通过的WinForms维护内部基准保持表单对象活着。这使约翰活着。

因此​​以正常的方式,这是清理向上的是,用户通过点击X在右上角关闭窗口。这又导致本地窗口句柄被破坏。这消除来自内部表的形式引用。接下来的垃圾收集,现在看到的只是循环引用,并收集他们。

I am trying to understand how events can cause a memory leak. I found a good explaination at this stackoverflow question but when looking at objects in Windg, I am getting confused with the result. To start with, I have a simple class as follows.

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);
        }
    }

Now I am using this class in a windows form application as follows.

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");
         }
    }

As you can see, I instaniated Person class and subscribed to UponWakingUp event. I have a button on this form. When user click this button, I set this Person instance to null without un-subscribing to the event. Then I call GC.Collect to be sure that Garbade collection is been performed. I am showing a message box here so that I can attach Windbg to look references help by Form1 class and within this class I don't see any reference to that event (Windbg output is shown below although Form1 has too long data, I am displaying related to my question). This class has a reference to Person class but it is null. Basically this does not seem like a memory leak to me as Form1 does not has any reference to Person class even thouh it did not unsubscribed to the event.

My question is if this does cause memory leak. If not, why not?

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  

解决方案

This is a case of a circular reference. The form has a reference to the object that listens to the event through the John field. In turn, John has a reference to the form when its UponWakingUp event was subscribed by the form's constructor.

Circular references can be a problem in certain automatic memory management schemes, particularly so in reference counting. But the .NET garbage collector doesn't have a problem with. As long as neither the form object nor the Person object have any additional references, the circular reference between the two cannot keep each other alive.

There are no additional references to either in your code. Which would normally cause both objects to be garbage collected. But the Form class is special, as long as a native Windows window for it exists, an internal reference stored in a handle-to-object table maintained by Winforms keeps the form object alive. Which keeps John alive.

So the normal way this is cleaned-up is that the user closes the window by clicking the X in the upper right corner. Which in turn causes the native window handle to be destroyed. Which removes the form reference from that internal table. The next garbage collection now sees nothing but the circular reference and collects them both.