【问题标题】:When WinForm releases its resources? (C#)WinForm什么时候释放它的资源? (C#)
【发布时间】:2009-07-11 15:32:48
【问题描述】:

这是一个 C# WinForm 问题。我有一个 MDI 子窗体,当窗体打开时,它会生成许多表以显示在 DataGridView 中。表可能很大,我使用 DataBinding 将表连接到 DataGridView。当我关闭表单时,我注意到表单占用的内存被及时回收。

我使用正常的方式来显示 MDI 子窗体:

var f = new FormBigMemory(objPassedIn);
f.Show();

如此处所示,我无法显式调用表单的 Dispose() 方法。我假设当 f 超出其生命周期时,.net 将自动回收它所占用的内存。

由于表单占用大量内存,我想显式调用 GC.Collect(); (我知道这样做可能不是一个好主意)。为此,我使用以下代码更改代码以在对话框模型中显示表单:

using(var f = new FormBigMemoryDialog(objPassedIn);
{
    f.ShowDialog();
}

GC.Collect();

看到表单占用的内存在调用 GC.Collect() 后没有被回收,我感到非常失望。在表单关闭并调用 GC.Collect() 后,我使用内存分析器工具获取内存快照。最大的内存由 BindingList 占用!我真的不明白:如果整个表单都被处理掉了,为什么BindingList 仍然存在于内存中。我检查了我的代码,没有任何 BindingList 引用从表单中泄露出来。

知道为什么这种有线的事情会发生在我身上吗?非常感谢任何关于 .net 内存管理的建议和技巧。

编辑:

感谢您的许多回答。让我澄清几点。

  1. 我并不是要处理 BindingList。 BindingList 在我的代码中用作数组,因此我希望看到 BindingList 持有的内存的回收。我不好不说清楚。

  2. 我明白不要自己调用 GC 操作。但就我而言,我想在表单关闭后立即明确地看到表单所占用的内存回收,无论如何要这样做?示例代码?

  3. 在我的问题中,我可以多次重复打开同一个表单,每次关闭表单时,内存消耗都会增加,直到 OutOfMemory 抛出。这显然是不对的。我期望看到的是,表单关闭后,内存回落到原来的水平,我可以在不增加内存的情况下反复打开和关闭表单。

编辑 2:

我进一步研究了代码。表单关闭后最大的数组(一个BindingList)不会被释放。它由 BindingSource 引用,而 BindingSource 由 EventHandler、CurrencyManager 和用户控件引用。 BindingSource 和用户控件都被(关闭的)表单引用,而(关闭的)表单被一些事件处理程序引用。

以上是我关闭表单后的内存快照。我使用对话框模型打开表单,关闭后调用其 Dispose() 方法,然后调用 GC.Collect() 强制回收内存。为什么在此之后表单实例仍然存在于我的内存快照中??

【问题讨论】:

  • 尽量不要假设问题出在 API 上。 GC 的测试比您的代码更彻底。

标签: c# .net winforms


【解决方案1】:

除非有充分的理由,否则切勿调用 GC.Collect。

我不知道细节,但这是我所知道的:

  • 删除所有引用后删除了某些内容。
  • GC 不会尝试一次删除所有内容,它不想干扰程序运行。
  • 表单很可能只是隐藏,而不是关闭。如何关闭表单?
  • 作为Mitch Wheat says,事件处理程序可能很顽固,因为您经常忘记删除它们。
  • Dispose 不会使表单从内存中删除,它会在表单内部调用普通方法,也许您可​​以在其中插入自己的清理逻辑。

【讨论】:

【解决方案2】:

您确定要取消连接表单使用的所有事件处理程序吗?

更新以响应 cmets 中的问题:每当对象向事件添加事件处理程序 (+= EventHandler) 时,当您完成对象实例时,应该有一个对应的 (-= EventHandler)。否则,它将作为堆中的根对象,垃圾回收不会回收。

【讨论】:

  • 这是常见的原因之一,但很容易被忽视,为什么表单或控件仍然存在,即使您认为它们应该已经消失了。
  • 取消连接事件处理程序是什么意思?是否有任何示例代码?我不相信任何控件在表单关闭后仍然存在,因为控件没有在许多地方共享,这是我的情况。
  • 仅在特定情况下才需要取消连接事件。我认为在大多数情况下,例如匿名按钮单击事件,都不需要拆线。因为这样的事件和表单是相互的循环引用,并且垃圾收集器足够强大,可以处理这样的循环引用。只有那些非循环引用需要不连线,这只有在订阅者的寿命比发布者长时才有帮助。我很难识别此类事件处理程序引用。但无论如何,这是一个好点。谢谢。我也怀疑数据绑定也应该被清除。
【解决方案3】:

为什么希望 Form.Dispose() 清理 BindingList?如果您从 System.ComponentModel 谈论 BindingList,它甚至不是一次性的。我不太确定 Form 处理其资源的深度。它可以遍历其 Controls 集合并在实现 IDisposable 的控件上调用 Dispose。但是,如果您在 DataSet 或 List 等 Form 上定义一个字段并用大量数据填充它,则对该 Form 调用 Dispose 不会对该字段占用的内存产生影响。如果您希望该字段被 GCed,那么您只需要确保它没有被任何地方引用

你如何使用 objPassedIn?这个对象是否有可能保留引用,从而阻止 GC 收集它们?我还建议在 FormClosing 事件中通过将对象设置为 null 和/或对它们调用 Dispose 来显式清理对象。正如@Mitch 所提到的,不取消订阅生命周期比您的表单更长的对象上的事件也可能是导致 GC 不收集您期望的对象的罪魁祸首。请记住,您可以在表单上调用 Dispose,但只要您有对表单的引用,它就会继续存在并且不会被垃圾回收。

TestForm frm = new TestForm();
frm.MyTextField = "Hello World";
frm.ShowDialog();
frm.Dispose();
string textField = frm.MyTextField;

这仍然有效。想象一下,您拥有的不是一个简单的文本字段,而是包含数千个对象的 List。但是,如果您尝试再次显示该表单,您将遇到问题,因为该表单需要用于正确显示的所有 UI 相关资源都已释放。

此外,假设您没有保留引用,则不需要显式强制垃圾回收。只要本身存在内存压力,就会发生 GC。就让它自然发生吧。如果您保留在您的情况下似乎是问题的引用,那么无论您调用 GC 多少次,它都不会收集您期望的东西,因为它们仍然被引用。

【讨论】:

    【解决方案4】:

    不要指望 .NET 会立即收集您的内存。

    阅读 thisthis 以更好地了解 .NET 如何处理 GC。

    查看this 以更好地了解如何解决您的问题。特别是阅读 “如果对象幸存会怎样”部分。

    【讨论】:

      【解决方案5】:

      垃圾收集器按自己的时间运行。

      WinForm 和 DataGridView 是独立的、不同的对象。

      BindingList 是 DataGridView 的一部分,与 Form 无关。

      当持有 DataGridView 的 WinForm 关闭时,您需要处理 DataGridView。

      DataGridView.Dispose() 应该在表单中的适当位置调用,可能是 FormClosing 事件。

      【讨论】:

        【解决方案6】:

        我对你唯一不同的是在创建新表单时使用 var。我会像普通定义一样使用类名。

        FormBigMemory f = new FormBigMemory(objPassedIn);
        f.MdiParent = this;
        f.Show();
        

        是否有可能通过使用 var (根据我的理解,这仅适用于泛型)您以某种方式保持内存可用?我不太明白这是怎么发生的,但可能值得谷歌搜索。

        或者也许通过不定义 MdiParent 来使表单在内存中保持活跃?

        如果其他人可以阐明我的想法,那就太好了。试着解决这个家伙的问题。

        【讨论】:

        • Var 只是一个关键字,它使编译器在编译时通过时间推断确定类型。结果代码将是相同的。
        • 我同意你的意见。但是您仍然要给编译器一些与泛型有特定用途的工作。我只是想知道稍后是否会在内存中使用有关该关键字的某些内容。只是戳戳并寻求答案。 :)
        • var 被引入该语言主要是为了支持匿名类型。它还使键入代码以实例化泛型类型变得更加容易,尤其是具有嵌套类型参数的类型。也就是说,这只是一种方便——即使是非泛型长类型名称也会从中受益。但是,生成的代码没有区别。 var 纯粹是一个编译时实体。
        猜你喜欢
        • 2011-03-23
        • 1970-01-01
        • 1970-01-01
        • 2020-10-13
        • 2013-03-23
        • 1970-01-01
        • 2013-07-27
        • 2021-12-09
        • 1970-01-01
        相关资源
        最近更新 更多