为什么最后一个关闭的MDI子窗体无法收集垃圾? [英] Why does the last MDI child form that was closed not get garbage collected?

查看:93
本文介绍了为什么最后一个关闭的MDI子窗体无法收集垃圾?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的应用程序中存在内存泄漏问题.我已经通过以下简单示例成功地复制了其中一个问题:

We've had problems with memory leaks in our application. I've managed to replicate one of the problems with the following simple example:

复制设置

1)创建以下帮助程序类,该类将用于跟踪对象的创建/销毁.

1) Create the following helper class which will be used to track object creation/destruction.

public class TestObject
{
    public static int Count { get; set; }

    public TestObject()
    {
        Count++;
    }

    ~TestObject()
    {
        Count--;
    }
}

2)用三个按钮创建一个MDI表单,第一个按钮将创建一个新的MDI子级,如下所示:

2) Create an MDI form with three buttons, the first button will create a new MDI child as follows:

    private void ctlOpenMDI_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.MdiParent = this;
        newForm.Tag = new TestObject();
        newForm.Show();
    }

将使用第二个按钮执行相同的操作,但使用非MDI子窗体:

The second button will be used do the same, but with a non-MDI child form:

    private void ctlOpenNonMDIForm_Click(object sender, EventArgs e)
    {
        Form newForm = new Form();
        newForm.Tag = new TestObject();
        newForm.Show();
    }

第三个按钮将用于垃圾收集,然后显示有多少个TestObject实例处于活动状态:

The third button will be used to garbage collect and then display how many TestObject instances are live:

    private void ctlCount_Click(object sender, EventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

        MessageBox.Show("Count: " + TestObject.Count);
    }

复制步骤

1)单击打开MDI表单"按钮,然后关闭MDI表单,然后单击计数"按钮.它将返回Count:1. MDI子窗体和它引用的对象没有被垃圾回收-某些东西仍然必须对其进行引用.

1) Click Open MDI form button, then close the MDI form, then click the count button. It will return Count: 1. The MDI child form and the object it references was not garbage collected - something must still have a reference to it.

也:

单击打开MDI表单"三次,关闭所有3个表单,然后单击计数"按钮.它将返回Count:1.似乎最后一个关闭的MDI子窗体没有被垃圾回收.

Click open MDI form three times, close all 3 forms, then click the count button. It will return Count: 1. It seems as though the last closed MDI child form is not garbage collected.

反例:

1)单击打开非MDI表单",将其关闭.然后单击计数按钮.它将返回Count:0,表单和对象已被垃圾回收.

1) Click Open non-MDI form, close it. Then click the count button. It will return Count: 0, the form and object have been garbage collected.

解决方法

我可以通过以下方法解决此问题:

I can workaround this problem by doing this:

        Form form = new Form();
        form.MdiParent = this;
        form.Show();
        form.Close();

在进行垃圾回收之前.这使该伪表单成为最后一个封闭的MDI子表单,以便可以对其他表单进行垃圾回收-但是为什么我必须这样做呢?发生了什么事?

Before the garbage collection. This makes this dummy form the last closed MDI child form so that the other ones can be garbage collected - but why should I have to do this? What is going on?

这也有点丑陋,因为您会忽然看到表单的打开和关闭,而且看起来也很笨拙.

Also it's a bit ugly as you will get a flicker of the form opening and closing, and it seems pretty hacky too.

推荐答案

从技术上讲,因为Form是"FormerlyActiveMdiChild".这看起来像个错误.幸运的是,这不是一个很严重的问题.

Technically, because that Form is the "FormerlyActiveMdiChild". This looks like a bug. Fortunately, not a very serious one.

对未收集的对象进行故障排除的能力是一项很好的技能. Windows调试工具附带的Microsoft windbg调试器( http://www.microsoft.com/whdc/devtools/debugging/default.mspx )非常适合此目的.在下面的演练中,请注意,我从windbg中删除了许多不相关的输出.

The ability to troubleshoot uncollected objects is a good skill to have. The windbg debugger from Microsoft that comes with the Debugging Tools for Windows (http://www.microsoft.com/whdc/devtools/debugging/default.mspx) is great for this purpose. In the walkthrough below, note that I have removed a lot of the output from windbg that is not pertinent.

  1. 与其创建类型为Form的MDI子实例,不如将其子类化为TestChildForm,以便于识别.
  2. 启动可执行文件并附加windbg.用!loadby sos mscorwks加载.NET扩展名.
  3. 在windbg中,运行!dumpheap -type TestChildForm.

  1. Instead of creating the MDI child instance of type Form, subclass it as TestChildForm to make it easy to identify.
  2. Start the executable and attach windbg. Load the .NET extensions with !loadby sos mscorwks.
  3. In windbg, run !dumpheap -type TestChildForm.

 Address       MT     Size
01e2e960 001c650c      320  

  • 下一步,运行!gcroot 01e2e960.

    ESP:3de7fc:Root:01e29a78(System.EventHandler)->
    01e26504(WindowsFormsApplication1.Form1)->
    01e269b8(System.Windows.Forms.PropertyStore)->
    01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
    

  • 下一步,运行!dumparray -details 01e2ef04并在输出中搜索01e2e960.

  • Next, run !dumparray -details 01e2ef04 and search the output for 01e2e960.

          MT    Field   Offset                 Type VT     Attr    Value Name
    6797ea24  40032a3       10         System.Int16  1 instance       56 Key
    6797ea24  40032a4       12         System.Int16  1 instance        1 Mask
    6798061c  40032a5        0        System.Object  0 instance 01e2e960 Value1
    

  • 最后,我先跑!name2ee System.Windows.Forms.dll System.Windows.Forms.Form,然后跑!dumpclass 6604cb84(由!name2ee确定),然后寻找56.

  • Finally, I ran !name2ee System.Windows.Forms.dll System.Windows.Forms.Form followed by !dumpclass 6604cb84 (as determined by !name2ee) and looked for 56.

          MT    Field   Offset                 Type VT     Attr    Value Name
    67982c4c  4001e80      fd8         System.Int32  1   static       56 PropFormerlyActiveMdiChild
    

  • 如果您想使用Visual Studio调试器代替windbg,则必须首先启用属性",调试"和启用非托管代码调试".将.load sos替换为.loadby sos mscorwks.

    If you would rather use the Visual Studio debugger instead of windbg, you must first enable Properties, Debug, Enable unmanaged code debugging. Substitute .load sos for .loadby sos mscorwks.

    这篇关于为什么最后一个关闭的MDI子窗体无法收集垃圾?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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