C# 为什么最后一个关闭的MDI子窗体没有被垃圾回收?
我们的应用程序中存在内存泄漏问题。我通过以下简单示例成功地复制了其中一个问题: 复制设置 1) 创建以下帮助器类,该类将用于跟踪对象的创建/销毁C# 为什么最后一个关闭的MDI子窗体没有被垃圾回收?,c#,winforms,forms,memory-leaks,mdichild,C#,Winforms,Forms,Memory Leaks,Mdichild,我们的应用程序中存在内存泄漏问题。我通过以下简单示例成功地复制了其中一个问题: 复制设置 1) 创建以下帮助器类,该类将用于跟踪对象的创建/销毁 public class TestObject { public static int Count { get; set; } public TestObject() { Count++; } ~TestObject() { Count--; } } 2) 使
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表单三次,关闭所有三个表单,然后单击计数按钮。它将返回Count:1。似乎最后一个关闭的MDI子窗体没有被垃圾收集
反案件:
1) 单击“打开非MDI表单”,然后将其关闭。然后单击计数按钮。它将返回计数:0,窗体和对象已被垃圾回收
解决方法
我可以通过以下方法解决此问题:
Form form = new Form();
form.MdiParent = this;
form.Show();
form.Close();
在垃圾收集之前。这使得这个虚拟表单成为最后一个关闭的MDI子表单,以便可以对其他子表单进行垃圾收集—但是为什么我必须这样做呢?发生了什么事
另外,它有点难看,因为你会看到表单打开和关闭的闪烁,而且看起来也很粗糙。从技术上讲,因为
表单
就是“FormerlyActiveMdiChild”。这看起来像个虫子。幸运的是,这不是一个非常严重的问题
对未收集的对象进行故障排除的能力是一项很好的技能。Windows调试工具()附带的Microsoft windbg调试器非常适合此用途。在下面的演练中,请注意,我已经从windbg中删除了许多不相关的输出
Form
的MDI子实例,而是将其子类化为TestChildForm
,以便于识别加载.NET扩展!sos mscorwks加载
!dumpheap-类型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[])
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
!dumparray-详细信息01e2ef04
,并在输出中搜索01e2e960
ESP:3de7fc:Root:01e29a78(System.EventHandler)->
01e26504(WindowsFormsApplication1.Form1)->
01e269b8(System.Windows.Forms.PropertyStore)->
01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])
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
发生这种情况的原因很简单,仍然有对该表单的引用。好消息是我们可以删除此引用
为表单关闭事件添加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列表中删除。现在,当表单关闭时,此引用也将重置。回答得很好!搜索该属性“FormerlyActivityChild”似乎确实表明它是最近引入的Microsoft错误。谢谢。这并不能解决这个问题。