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

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

问题描述

我试图了解事件如何导致内存泄漏.我在 this 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 类并订阅了 OnWakingUp 事件.我在这个表格上有一个按钮.当用户单击此按钮时,我将此 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\WindowsFormsApplication1WindowsFormsApplication1inDebugWindowsFormsApplication1.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 字段引用了侦听事件的对象.反过来,当表单的构造函数订阅了它的 OnWakingUp 事件时,John 有一个对表单的引用.

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 维护的句柄到对象表中的内部引用就会使表单对象保持活动状态.这让约翰活着.

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天全站免登陆