2
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 类使用 - GCHandle.Alloc 请求 GC 不收集。伙计们,这是内存泄漏的一大来源。除非我设置 t.Enabled = false,否则即使我们关闭表单,整个表单也会被泄露。

在第一个示例代码中,我无法理解为什么即使在我触发 GC.Collect() 之后表单也没有被垃圾收集。在反射器中,我看到 ControlNativeWindow 已在内部使用 GCHandle.Alloc 的 Form 中使用。是这个原因吗。。作为 .NET 库的用户,我始终相信,当一个引用无法访问时,它就会有机会进行垃圾收集。当然,垃圾收集和从内存中的实际释放是不确定的。但我的问题是——我对这两个例子的理解是否正确?如果有对象即使在无法访问后仍然可以生存,那么我将如何跟踪它以防止内存泄漏?

4

2 回答 2

4

Winforms 保留一个将句柄映射到控制实例的内部表。该表确保只要本机窗口处于活动状态,控件(在您的情况下为表单)就永远不会被垃圾收集。当窗口被销毁时,它会从该表中删除,无论是用户关闭表单还是您的代码处理它。

System.Timers.Timer 由 CLR 引用的 cookie 保持活动状态。该类是用 System.Threading.Timer 实现的,它有一个接受状态对象参数的构造函数。该状态对象是 cookie,CLR 使用 GCHandle.Alloc() 的等价物对其进行引用。禁用计时器会重置允许计时器被垃圾收集的cookie。

这些是框架防止这些对象过早收集垃圾的自然且必要的方法。您只能通过在处理表单时忘记禁用计时器来导致泄漏。这通常是非常不健康的,当表单死亡时,您不希望计时器继续滴答作响。将 Dispose 方法从 Designer.cs 文件移动到表单代码中或重写 OnFormClosed() 以禁用计时器。

于 2013-01-31T02:27:52.403 回答
1

您必须自己管理实例的生命Form周期System.Windows.Forms.Timer。你需要打电话Dispose来标记他们的生命结束,然后GC再收集他们。在内部关闭表单调用Dispose。如果您的计时器是使用设计器放置在表单上的,那么计时器将在表单关闭时被释放。这是通过设计器在表单构造函数中生成以下代码来实现的:

this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);

形式Dispose为:

protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
        components.Dispose();
    }
    base.Dispose(disposing);
}

因此,实际上只要您关闭表单而不只是隐藏它们,就没有内存泄漏。但是,如果您手动创建了计时器,那么您必须在表单关闭时自行处理它。

另一方面,System.Threading.Timer如果你没有对它的任何引用,即使你没有Dispose它,也会被垃圾收集。

于 2013-01-31T02:28:13.823 回答