为什么最后一个关闭的 MDI 子窗体没有被垃圾回收?
我们的应用程序中存在内存泄漏问题。我已经成功地通过以下简单示例复制了其中一个问题:
复制设置
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 子表单,以便其他表单可以被垃圾收集 - 但为什么我必须这样做呢?到底是怎么回事?
而且它有点难看,因为你会看到表单打开和关闭时闪烁,而且看起来也很老套。
We've had problems with memory leaks in our application. I've managed to replicate one of the problems with the following simple example:
Replication setup
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) 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();
}
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();
}
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);
}
Replication steps
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.
Also:
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.
Counter-cases:
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.
Workaround
I can workaround this problem by doing this:
Form form = new Form();
form.MdiParent = this;
form.Show();
form.Close();
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
从技术上讲,因为
Form
是“FormerlyActiveMdiChild”。这看起来像一个错误。幸运的是,不是很严重。对未收集的对象进行故障排除的能力是一项很好的技能。 Microsoft 的 Windbg 调试器附带 Windows 调试工具 (http:// www.microsoft.com/whdc/devtools/debugging/default.mspx)非常适合此目的。在下面的演练中,请注意,我删除了 Windbg 中许多不相关的输出。
Form
类型的 MDI 子实例,而是将其子类为TestChildForm
以使其易于识别。!loadby sos mscorwks
加载 .NET 扩展。在windbg中,运行
!dumpheap -type TestChildForm
。接下来,运行
!gcroot 01e2e960
。接下来,运行
!dumparray -details 01e2ef04
并在输出中搜索01e2e960
。最后,我运行了
!name2ee System.Windows.Forms.dll System.Windows.Forms.Form
,然后运行了!dumpclass 6604cb84
(由! name2ee
)并查找 56。如果您更愿意使用 Visual Studio 调试器而不是 Windbg,则必须首先启用“属性”、“调试”、“启用非托管代码调试”。将
.load sos
替换为.loadby sos mscorwks
。Technically, because that
Form
is the "FormerlyActiveMdiChild". This looks like a bug. Fortunately, not a very serious one.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.
Form
, subclass it asTestChildForm
to make it easy to identify.!loadby sos mscorwks
.In windbg, run
!dumpheap -type TestChildForm
.Next, run
!gcroot 01e2e960
.Next, run
!dumparray -details 01e2ef04
and search the output for01e2e960
.Finally, I ran
!name2ee System.Windows.Forms.dll System.Windows.Forms.Form
followed by!dumpclass 6604cb84
(as determined by!name2ee
) and looked for 56.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
.发生这种情况的原因很简单,仍然引用了这种形式。好消息是我们可以删除这个引用。
为表单关闭事件添加事件处理程序。
以及处理事件的方法。
这里我们重置 MdiParent 属性,通过这样做,表单将从父级的 MdiChild 列表中删除。现在,当表单关闭时,该引用也将重置。
The reason why this is happening is quite simple there is still a reference to this form. The good news is that we can remove this reference.
Add an eventhandler for the form closing event.
And a method to handle the event.
Here we reset the MdiParent property, by doing this the form is removed from the MdiChild list of the parent. Now when the form is closed this reference will reset as well.