【问题标题】:C# Garbage Collection in windows formsWindows 窗体中的 C# 垃圾收集
【发布时间】:2017-09-22 00:56:34
【问题描述】:

我有 2 个窗体 frmMain 和 frmDialog。当我单击 frmMain 上的按钮时,会打开 frmDialog。这是我的按钮点击事件代码:

frmDialog f2 = new frmDialog();

f2.Show();

当我继续点击按钮时,会出现新的表单并且不要关闭。对象超出范围时会被垃圾回收。

我的问题是:

为什么变量 f2 在超出范围时不被垃圾回收?

是内存泄漏吗?

【问题讨论】:

  • 你关闭过表单吗?你的问题没有说清楚。为什么“new frmDialog()”不应该创建一个新的 Form 实例?
  • 垃圾回收与显示窗口无关。此外,“对象超出范围时会被垃圾收集”是不正确的。更接近正确的说法是“当可以证明不再使用该对象时,该对象成为有资格收集”。这可以早于“范围”的结束,也只是声明资格。无法保证何时会收集该对象。
  • @Smartis - 只要“你”,无论你用这个词是什么意思,都不符合收集条件。
  • 当我关闭 frmMain 时,frmMain 以及所有对话框都会关闭

标签: c# garbage-collection


【解决方案1】:

有个窍门。当您调用Show 时,属性Visible 设置为true,进而调用SetVisibleCorehttp://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Form.cs,b31b076f655b0b4b

有趣的是:

if (CalledOnLoad) {
    // VSWhidbey 518085: make sure the form is in the Application.OpenForms collection
    if (!Application.OpenFormsInternal.Contains(this)) {
        Application.OpenFormsInternalAdd(this);
    }
}

表单被添加到存储在静态属性Application.OpenFormsInternal 中的集合中。因此,您的表单将被植根,并且在关闭之前没有资格进行垃圾收集(并自动从该收集中删除)。


按照 Hans Passant 的评论,我玩了一下内存转储:

0:000> !gcroot 000000000263d368
HandleTable:
0000000000191348 (strong handle)
-> 000000000263d368 WindowsFormsApp1.Form1

00000000001917d0 (pinned handle)
-> 00000000125f99a8 System.Object[]
-> 000000000262c9a8 System.Windows.Forms.FormCollection
-> 000000000262c9d8 System.Collections.ArrayList
-> 000000000262ca00 System.Object[]
-> 000000000263d368 WindowsFormsApp1.Form1

我们看到Application.OpenFormsInternal 集合(第二个根),但也有一个强大的句柄,即使它不存在于集合中(apparently could happen)也可以保持表单处于活动状态。

【讨论】:

  • 看起来很有说服力,但it is not correct。参考保持在低得多的水平。查看 NativeWindow.CreateHandle(),你会发现 HandleCollector 类。
  • @HansPassant 哦。我停在第一个根,我想我应该挖得更深。
【解决方案2】:

是的,这是内存泄漏。

在 C# 中,您有两种内存。托管内存和非托管内存。托管内存由 GC 管理 :) 非托管不是。您必须自己释放非托管内存。例如(托管):

{
    List<int> l = new List<int(); 
}

这不会造成内存泄漏。在 l 超出范围后,它被标记为被释放。 标记为未释放。它可能稍后会被释放(然后将调用列表析构函数)。

那么非托管内存呢?

{
    MyForm f = new MyForm();
}

这是内存泄漏。当 f 超出范围时,它的 非托管内存 根本不受 GC 管理。所以你必须自己释放它:

{
    MyForm f = new MyForm();
    f.ShowDialog();
    f.Dispose();
}

Dispose 方法释放所有非托管内存。所以现在你可能有两个问题: 1. 我如何知道一个类是托管的还是非托管的? 这很简单。您可以假设所有实现 IDisposable 接口(包含 Dispose() 方法)的类都是非托管的 - 它们本身是非托管的,或者它们由非托管类组成。尤其是字体、流、表单、数据库连接等所有资源。

  1. 我应该如何释放无模式表单? 您可以存储对该表单的引用并在不再需要时处理它。您可以在 FormClosed 事件中调用 Dispose (https://msdn.microsoft.com/en-us/library/system.windows.forms.form.formclosed.aspx)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 2011-01-21
    相关资源
    最近更新 更多