سؤال

As I learned from online and my personal experiment, the finalizer of a form (System.Windows.Forms.Form) never gets called by GC. It is said that inside the Dispose() of Form GC.SuppressFinalize() is called so that finalizer wouldn't gets called again.

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
    }
}

However, as the example above shows, if the form connects(+=) a event of some model and doesn't disconnect(-=) the event in Dispose(), the form would never be garbage collected, even if the Dispose() gets called.

What I do to check if the form is really garbage collected is that I create a big array inside the form to consume a lot of memory as below:

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

Then I look at the Memory Profile of Task Manager in Windows to see if the memory usage is reduced when I call GC.Collect() after the form is disposed.

My method is not smart, and I wonder if there is other smarter way or some tools to confirm the form is actually garbage collected? Thanks.

هل كانت مفيدة؟

المحلول 2

You can keep a weak reference to your form using the WeakReference class:

var weakref = new WeakReference(form);

A weak reference does not prevent the object from being garbage collected, and you can use that property to check if it has been:

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

The form does not need a finalizer for this to work.

نصائح أخرى

    Database.OnDataUpdated += new EventHandler(DataBase_OnDataUpdated);

Yes, that's a problem. The generic diagnostic is that the event source object out-lives the event listener object. Or in other words, your form object keeps listening to database updates, even after it was closed by the user. This usually causes an exception, the most typical way in which you discover the problem, boilerplate is an ObjectDisposedException when your event handler tries to update the disposed controls. It isn't that clear how you managed to avoid this failure mode, do make sure you didn't paper-over that failure mode with, say, a try/catch.

And, yes, it causes a GC problem. The Database object has a reference to your form object. You gave it that reference when you subscribed the event. And uses it again, later, when it fires the event. Necessary because your DataBase_OnDataUpdated() method is an instance method of your class. The C# syntax sugar hides that fact. The actual code underneath that simple event assignment statement looks like this (not valid C# code):

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

It is the hidden this in the delegate constructor call that passes the reference to your form object to the Database object. Which stores it in the Delegate.Target field. To be used later when it fires the event.

So, inevitably, the GC sees a reference to your form object even after it is closed. It finds it back in the delegate invocation list of the Database object. So the form object cannot be garbage collected until the Database object is garbage collected. Which, judging from your question, doesn't happen until your program terminates. Probably because it is a static variable.

There are other patterns that avoid this problem. You could for example pass a reference to your form to the Database class, which can store it in a list of active forms that are listening to notifications. It can subscribe the form's Disposed event to know that the form is dead and remove the object from that list. You'd also need to have your form implement an interface, methods that the Database class calls when something interesting happens. The antipode of the observer pattern, otherwise not exactly as pretty as using events.

Or just punt the problem, since you now know what causes it. Simply unsubscribe the event explicitly:

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

Note how this exact same kind of code is required when you subscribe to other event sources in .NET that outlive their listeners. Like the events raised by SystemEvents and Application.Idle

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top