3

Let's assume that inside a Windows Form, I start a long-running task like this:

ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateDailyTasksChart));

Then, this function runs for a while, but I close the window before it finishes.

private void UpdateDailyTasksChart (object param)
{
  //Call Data Access Layer, go get MySQL Data. But I close the Form.
  UICallback(chartData); //The Form is closed when this is executed.
}

And now here's what UICallback does:

private delegate void UICallbackDel(object chartData);
private void UICallback (object chartData)
{
  if (InvokeRequired)
  {
    this.Invoke(new UICallbackDel(UICallback), chartData);
  }
  else
  {
    aButtonOnMyForm.Visible = false; //But the Form has been closed!
  }
}

Curiously, this code doesn't crash.

I placed breakpoints in the Form_Closed event and it does execute. I haven't checked if the Form still exists by, for example, declaring it with a class variable. But my guess is that it does.

So the question is: the GC will only collect the Form when my thread finishes? Or what does it happen?

Axonn
  • 10,076
  • 6
  • 31
  • 43
  • Has the form been disposed explicitly? – Jon Mar 04 '12 at 17:44
  • It works by accident. You are lucky, the Visible property is already false when the form is disposed so nothing is actually being done. It does not exclude the bombs-once-a-month threading race possibility. – Hans Passant Mar 04 '12 at 19:07
  • @Hans Passant: But if the Form has been disposed, simply accessing the Visible property would crash, wouldn't it? Anyway, the .Visible line is only one in many. I got a few other functions executing there. Several changes are done to the Form when the Thread finishes its execution and none of those lines crash. But since I didn't dispose of the Form explicitly, I guess that, as Phong said, the GC didn't have to do its job yet, so the Form is still there, waiting for the Thread to end. Which is annoying. I'll have to give up the ThreadPool and use a Thread object which allows for more control. – Axonn Mar 04 '12 at 21:39
  • It can't be garbage collected, you are keeping a reference to the form (somewhere) that allows you to call the instance method. Which in itself is a problem, it is a leak. Not a small one either, the Form class object is a biggy. The only sane way to deal with this is to actually prevent to the form from closing until you *know* that the thread cannot invoke anymore. Covered by this answer: http://stackoverflow.com/questions/1731384/how-to-stop-backgroundworker-on-forms-closing-event/1732361#1732361 – Hans Passant Mar 04 '12 at 22:13
  • @Hans Passant: I would prefer being able to close the Form and abort the thread completely. Although I wonder how I could accomplish this without passing any reference to the Form to the thread, which would prevent it from being GC-ed. Perhaps aborting a Thread in the Closing event would work? – Axonn Mar 05 '12 at 09:14

3 Answers3

4

There are two points here:

  1. If you explicitly dispose of the form you may expect to raise an ObjectDisposedException when the work item delegate executes.
  2. Your work item delegate holds a reference to the form through this, InvokeRequired (which in turn references this) and aButtonOnMyForm. Therefore, your form is ineligible for garbage collection as long as the ThreadPool thread has not completed execution. That's why the code doesn't crash. (Note: this is always implicitly accessible regardless.)

Another thing to derive from this is that it is generally wise to unregister external event handlers from a form before that form closes, lest you create memory leaks. The big gotcha there is if you register a lambda expression or anonymous delegate, you need to save off a reference to it in order to unregister later. Simply copying and pasting the same code would not work.

  • 2
    Thank you. I guess I should use a Thread object or a BackgroundWorker. Anyway, something I can abort when the window is closed. – Axonn Mar 04 '12 at 21:40
2

You may want to take a look at this post.

Basically, you may or may not get an ObjectDisposedException depending on what the GC is doing at the time you are trying to Invoke on the form. You can try and catch it but it cannot be tested reliably, so that is more of a hack than anything.

Community
  • 1
  • 1
Bryan Crosby
  • 6,486
  • 3
  • 36
  • 55
  • That doesn't make any sense. If you still have a reference to the form, it's clear what will GC do to it: nothing. – svick Mar 04 '12 at 18:19
2

The garbage collector doesn't care whether an object is “closed”, or “disposed” or anything like that. It only cares whether the object is still accessible. And in your case, the form is still accessible by using this (implicitly or explicitly). That's why your application doesn't crash.

Of course, if an object is closed or disposed or something like that, it's within its rights to throw ObjectDisposedException, or similar for any method you call on it. But it certainly doesn't have to do that.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Since I don't like disposing explicitly, I'll probably solve this by employing an "abortable" Thread. – Axonn Mar 04 '12 at 21:40
  • As long as you don't mean calling `Thread.Abort()`, then I guess that should work. – svick Mar 04 '12 at 21:43
  • 1
    That's exactly what I meant. Why isn't that good? For me, it's more important to allow the user to close the window than the thread to complete, because it's a chart: i.e. not a critical operation. If the user closes the window, then I just give up on everything and that's it. – Axonn Mar 05 '12 at 08:39
  • [Because `Thread.Abort()` can easily leave your application in an inconsistent state.](http://stackoverflow.com/questions/421389/is-this-thread-abort-normal-and-safe) Instead of calling that method, you can set a flag that tells you not to invoke the action on the form. – svick Mar 05 '12 at 09:42
  • But this will keep the thread working even after the Form is closed. Even more, the Form won't be GC-ed until the Thread finishes, isn't it? – Axonn Mar 05 '12 at 15:59