垃圾收集和GCHandle.Alloc

时间:2013-01-31 02:11:37

标签: c# garbage-collection

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,否则即使我们关闭表格,整个表格也会被泄露。

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

2 个答案:

答案 0 :(得分:4)

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

System.Timers.Timer由CLR引用的cookie保持活动状态。该类使用System.Threading.Timer实现,该系统具有一个带有 state 对象参数的构造函数。 state 对象是cookie,CLR使用等效的GCHandle.Alloc()来引用它。禁用计时器会重置cookie,从而允许对计时器进行垃圾回收。

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

答案 1 :(得分:1)

您必须自己管理FormSystem.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,则会对其进行垃圾收集。