void Foo()
{
System.Windows.Forms.Form f = new System.Windows.Forms.Form();
f.Show();
}
据我了解,f 持有对表单的引用。但 f 是一个局部变量,当控件离开花括号时它将超出范围。但表格仍然开放。我尝试调用 GC.Collect(),但表单仍然打开。
还有一个场景。
private void button2_Click(object sender, EventArgs e)
{
Timer t = new Timer();
t.Enabled = true;
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
}
在这种情况下,t永远不会被垃圾收集。经过大量研究,我发现当我设置 t.Enabled = true 时,Timer 类正在请求 GC 不要使用 - GCHandle.Alloc 进行收集。伙计们,这是内存泄漏的一个重要来源。除非我设置 t.Enabled = false,否则即使我们关闭 Form,整个 Form 也会被泄露。
在第一个示例代码中,我无法理解为什么即使在触发 GC.Collect() 后表单也没有被垃圾收集。在反射器中,我看到 ControlNativeWindow 已在内部使用 GCHandle.Alloc 的 Form 中使用。这是一个原因吗?作为 .NET 库的用户,我始终相信,当引用无法访问时,它将有机会进行垃圾回收。当然,垃圾收集和内存的实际释放是不确定的。但我的问题是 - 我对这两个例子的理解是否正确?当有些对象即使在无法访问后仍然可以生存时,我将如何跟踪它以防止内存泄漏?
最佳答案
Winforms 保留一个将句柄映射到控件实例的内部表。该表确保只要 native 窗口处于事件状态,控件(您的情况下的表单)就永远不会被垃圾收集。当窗口被销毁时,它会从该表中删除,无论是通过用户关闭表单还是通过代码处理它。
System.Timers.Timer 通过 CLR 引用的 cookie 保持事件状态。该类是用 System.Threading.Timer 实现的,它有一个采用 state 对象参数的构造函数。该state对象就是cookie,CLR使用相当于GCHandle.Alloc()的方法来保持它的引用。禁用计时器会重置 cookie,从而允许计时器被垃圾收集。
这些是框架防止这些对象过早被垃圾收集的自然且必要的方法。只有在处理表单时忘记禁用计时器才会导致泄漏。一般来说,这是非常不健康的,您不希望计时器在表单死亡时继续滴答作响。将 Dispose 方法从 Designer.cs 文件移至表单代码中或重写 OnFormClosed() 以禁用计时器。
关于c# - 垃圾收集和 GCHandle.Alloc,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14617336/