3

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

复制设置

1) 创建以下帮助类,用于跟踪对象的创建/销毁。

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

    public TestObject()
    {
        Count++;
    }

    ~TestObject()
    {
        Count--;
    }
}

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

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

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

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

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

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

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

复制步骤

1) 点击 Open MDI form 按钮,然后关闭 MDI form,然后点击 count 按钮。它将返回 Count: 1. MDI 子窗体和它引用的对象没有被垃圾回收 - 某些东西必须仍然具有对它的引用。

还:

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

反例:

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

解决方法

我可以通过这样做来解决这个问题:

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

在垃圾收集之前。这使得这个虚拟表单成为最后一个关闭的 MDI 子表单,以便其他表单可以被垃圾收集 - 但为什么我必须这样做?到底是怎么回事?

它也有点难看,因为你会看到表单打开和关闭的闪烁,而且看起来也很hacky。

4

2 回答 2

3

从技术上讲,因为那Form是“FormerlyActiveMdiChild”。这看起来像一个错误。幸运的是,不是很严重。

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

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

     Address       MT     Size
    01e2e960 001c650c      320  
    
  4. 接下来,运行!gcroot 01e2e960.

    ESP:3de7fc:Root:01e29a78(System.EventHandler)->
    01e26504(WindowsFormsApplication1.Form1)->
    01e269b8(System.Windows.Forms.PropertyStore)->
    01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
    
  5. 接下来,运行!dumparray -details 01e2ef04并搜索输出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
    
  6. 最后,我!name2ee System.Windows.Forms.dll System.Windows.Forms.Form跟着!dumpclass 6604cb84(由 确定!name2ee)并寻找 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

于 2009-10-12T23:20:23.390 回答
0

发生这种情况的原因很简单,仍然有对这种形式的引用。好消息是我们可以删除这个引用。

为表单关闭事件添加事件处理程序。

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

以及处理事件的方法。

private void form_Closing(object sender, EventArgs e)
{
    Form form = sender as Form;
    form.MdiParent = null;
}

在这里,我们重置了 MdiParent 属性,通过这样做,表单从父级的 MdiChild 列表中删除。现在,当表单关闭时,此引用也将重置。

于 2010-03-18T13:42:35.790 回答