【问题标题】:Garbage collection and GCHandle.Alloc垃圾收集和 GCHandle.Alloc
【发布时间】:2013-01-15 01:20:21
【问题描述】:
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,否则即使我们关闭 Form,整个 Form 也会被泄露。

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

【问题讨论】:

  • 几乎没有巨大的内存泄漏.. 除非您没有让我们意识到存在巨大的生命周期/性能问题。此外,您的活动还有订阅者,不是吗?
  • 当显示一个表单时,实际上可以通过System.Windows.Forms.Application.OpenForms获取打开的表单列表。你很难说没有对表格的引用。
  • @SimonWhitehead 正如 Alvin Wong 所指出的,对于表单,可能在其他地方有引用。对于计时器示例,订阅者无法阻止垃圾回收。
  • @AlvinWong 这很有道理。
  • 这让我想到了另一件事:实现IDisposable 的类拥有非托管资源,您需要显式调用Dispose 来释放它们。否则,可能会发生内存泄漏。

标签: c# garbage-collection


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您向我解释这种行为。用户可以看到一个表单,我可以依靠用户来关闭和处理它。对于计时器,如果我忘记处置计时器,托管和非托管都会出现资源泄漏。对于普通用户来说,这是令人困惑的。调用 Dispose() 似乎对所有 IDisposable 对象都是强制性的。
  • 你必须停止计时器,仅此而已。不停止它是一个错误,而不是资源泄漏。
【解决方案2】:

您必须自己管理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 它。

【讨论】:

  • 我尝试投票,但我在弹出窗口中收到一条错误消息,提示发生错误 - 请重试您的请求。所以我稍后再试。
  • 我是这个论坛的新手。我需要 15 次代表才能投票。但是给我一些时间,我会再次回到这里:)
  • 没有必要对每个 Timer 都进行 Dispose()。但是您必须在 Form.OnClosed() 中停止所有 Timer。
猜你喜欢
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
相关资源
最近更新 更多