为什么当事件未被取消订阅时,这不会导致内存泄漏 [英] Why this does not cause a memory leak when event is not unsubscribed

查看:226
本文介绍了为什么当事件未被取消订阅时,这不会导致内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试了解事件如何导致内存泄漏。我在这个的stackoverflow问题,但是当看到Windg的对象时,我对这个结果感到困惑。首先,我有一个简单的类如下。

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

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

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

正如你所看到的,我把Person类和订阅到UponWakingUp事件。我在这个表单上有一个按钮。当用户单击此按钮时,我将此Person实例设置为null,而不取消订阅该事件。然后我调用GC.Collect来确保Garbade集合被执行了。我在这里显示一个消息框,以便我可以附加Windbg来查看Form1类的引用帮助,并且在此类中我没有看到任何对该事件的引用(Windbg输出如下所示,尽管Form1具有太长的数据,我正在显示与我的问题有关)。这个类有一个Person类的引用,但它是空的。基本上,这似乎不是一个内存泄漏给我,因为Form1没有任何引用Person类,即使它没有取消订阅事件。

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  


推荐答案

这是一个循环引用的例子。该表单引用了通过 John 字段侦听事件的对象。反过来,约翰在表单的构造函数订阅了它的UponWakingUp事件时,引用了表单。

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.

循环引用可能是某些自动内存管理方案中的问题,特别是在引用计数中。但是.NET垃圾回收器没有问题。只要表单对象和Person对象都没有任何其他引用,两者之间的循环引用就不能保持彼此存在。

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.

没有任何附加引用在你的代码这通常会导致两个对象被垃圾回收。但是Form类是特殊的,只要它的本机Windows窗口存在,存储在由Winforms维护的句柄对象表中的内部引用将使表单对象保持活动。这让John活着。

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.

所以清理的常规方法是用户通过点击右上角的X关闭窗口。这又导致本机窗口句柄被破坏。它从内部表中删除表单引用。下一个垃圾收集现在看不到循环引用,并收集它们。

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.

这篇关于为什么当事件未被取消订阅时,这不会导致内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆