如何确保winform被垃圾收集?

时间:2013-07-14 06:29:37

标签: c# winforms memory garbage-collection finalizer

正如我从在线和我的个人实验中学到的那样,表格(System.Windows.Forms.Form)的终结器永远不会被GC调用。 据说,在Form GC.SuppressFinalize()的Dispose()内部调用,以便不再调用终结器。

Exapmle:

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

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

2 个答案:

答案 0 :(得分: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);

委托构造函数调用中隐藏的 this 将表单对象的引用传递给Database对象。它将它存储在Delegate.Target字段中。以后在它发生事件时使用。

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

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

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

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

注意当您订阅.NET中比其监听器更长的其他事件源时,如何使用这种完全相同的代码。与SystemEvents和Application.Idle

引发的事件一样

答案 1 :(得分:0)

您可以使用WeakReference class

保留对表单的弱引用
var weakref = new WeakReference(form);

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

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

表单不需要终结器才能工作。