我们的应用程序中存在内存泄漏问题。我已设法通过以下简单示例复制其中一个问题:
复制设置
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)单击“打开MDI表单”按钮,然后关闭MDI表单,再单击“计数”按钮。它将返回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。
答案 0 :(得分:3)
从技术上讲,因为Form
是“FormerlyActiveMdiChild”。这看起来像一个bug。幸运的是,不是一个非常严肃的人。
对未收集的对象进行故障排除的能力是一项很好的技能。 Microsoft的windbg调试器附带Windows调试工具(http://www.microsoft.com/whdc/devtools/debugging/default.mspx)非常适合此目的。在下面的演练中,请注意我已经从windbg中删除了很多不相关的输出。
Form
类型的MDI子实例,而是将其子类化为TestChildForm
,以便于识别。!loadby sos mscorwks
加载.NET扩展。在windbg中,运行!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
的输出。
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。
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
。
答案 1 :(得分:0)
之所以发生这种情况非常简单,仍然可以参考这种形式。好消息是我们可以删除此引用。
为表单结束事件添加eventhandler。
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列表中删除。现在,当表单关闭时,此引用也将重置。