3

正如我从网上和我的个人实验中了解到的,GC 永远不会调用表单的终结器(System.Windows.Forms.Form)。据说在 Form GC.SuppressFinalize() 的 Dispose() 内部被调用,这样终结器就不会被再次调用。

示例:

public partial class UpdateForm : Form
{
    public UpdateForm()
    {
        InitializeComponent();

        // Listen to the event of some model
        Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);
    }

    ~UpdateForm()
    {
        // Never gets called.
    }

    private void DataBase_OnDataUpdated(object sender, EventArgs e)
    {
        // Update data on this form
    }
}

但是,如上面的示例所示,如果表单连接(+=)某个模型的事件并且没有断开(-=) Dispose() 中的事件,则表单永远不会被垃圾收集,即使 Dispose( ) 被调用。

我检查表单是否真的被垃圾收集的方法是在表单内创建一个大数组以消耗大量内存,如下所示:

 int[] dummyArray = new int[1024 * 1024 * 128]; // Comsume 128MB memory

然后我查看 Windows 中任务管理器的内存配置文件,看看我在处理表单后调用 GC.Collect() 时内存使用量是否减少。

我的方法不聪明,我想知道是否有其他更聪明的方法或一些工具来确认表单实际上是垃圾收集?谢谢。

4

2 回答 2

3
    Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);

是的,这是个问题。通用诊断是事件源对象比事件侦听器对象寿命长。或者换句话说,你的表单对象一直在监听数据库更新,即使它被用户关闭了。这通常会导致异常,这是您发现问题的最典型方式,当您的事件处理程序尝试更新已处置的控件时,样板文件是 ObjectDisposedException。目前尚不清楚您是如何设法避免这种故障模式的,但请确保您没有使用 try/catch 来覆盖该故障模式。

而且,是的,它会导致 GC 问题。Database 对象引用了您的表单对象。您在订阅活动时给了它参考。稍后在触发事件时再次使用它。必要,因为您的 DataBase_OnDataUpdated() 方法是您的类的实例方法。C# 语法糖隐藏了这一事实。该简单事件赋值语句下的实际代码如下所示(不是有效的 C# 代码):

var delegateObject = new EventHandler(this, &DataBase_OnDataUpdated);
Database.OnDataUpdated = Delegate.Combine(DataBase_OnDataUpdated, delegateObject);

这是隐藏的这个将对表单对象的引用传递给 Database 对象。它将它存储在 Delegate.Target 字段中。稍后在触发事件时使用。

因此,GC 不可避免地会看到对您的表单对象的引用,即使在它关闭之后也是如此。它在 Database 对象的委托调用列表中找到它。所以在数据库对象被垃圾收集之前,表单对象不能被垃圾收集。从您的问题来看,在您的程序终止之前不会发生这种情况。可能是因为它是一个静态变量。

还有其他模式可以避免这个问题。例如,您可以将对表单的引用传递给 Database 类,该类可以将其存储在正在侦听通知的活动表单列表中。它可以订阅表单的 Disposed 事件以知道表单已失效并从该列表中删除该对象。你还需要让你的表单实现一个接口,当有趣的事情发生时数据库类调用的方法。观察者模式的对立面,否则不如使用事件漂亮。

或者只是解决问题,因为您现在知道是什么原因造成的。只需明确地取消订阅事件:

    protected override void OnFormClosed(FormClosedEventArgs e) {
        Database.OnDataUpdated -= DataBase_OnDataUpdated;
        base.OnFormClosed(e);
    }

请注意,当您在 .NET 中订阅比其侦听器寿命更长的其他事件源时,如何需要完全相同类型的代码。像 SystemEvents 和 Application.Idle 引发的事件

于 2013-07-14T10:41:01.540 回答
0

您可以使用Wea​​kReference 类保持对表单的弱引用:

var weakref = new WeakReference(form);

弱引用不会阻止对象被垃圾收集,您可以使用该属性来检查它是否已经:

if (weakref.IsAlive) { /* not yet garbage collected */ }

表单不需要终结器就可以工作。

于 2013-07-14T06:59:30.563 回答