如何确保winform被垃圾收集

本文关键字:winform 何确保 确保 | 更新日期: 2023-09-27 18:22:18

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

示例:

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()时内存使用量是否减少了。

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

如何确保winform被垃圾收集

    Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);

是的,这是个问题。一般诊断是事件源对象超出了事件侦听器对象的寿命。换句话说,即使在用户关闭表单对象之后,表单对象也会继续侦听数据库更新。这通常会导致异常,这是发现问题的最典型方式,当事件处理程序尝试更新已处理的控件时,样板是ObjectDisposedException。目前还不清楚你是如何避免这种失败模式的,一定要确保你没有用尝试/捕捉等方法掩盖这种失败模式。

是的,它会导致GC问题。数据库对象引用了您的表单对象。您在订阅活动时向其提供了该参考。并在以后触发事件时再次使用它。必需,因为您的DataBase_ODataUpdated()方法是类的实例方法。C#语法糖隐藏了这一事实。这个简单事件赋值语句下面的实际代码如下(不是有效的C#代码):

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

正是委托构造函数调用中隐藏的this将对表单对象的引用传递给数据库对象。它将其存储在Delegate.Target字段中。稍后在触发事件时使用。

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

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

或者只是抛出问题,因为你现在已经知道是什么原因了。只需明确取消订阅事件:

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

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

您可以使用WeakReference类保留对表单的弱引用:

var weakref = new WeakReference(form);

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

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

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