Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly dispose a Form, without risk of an Invoke being called from another thread on a disposed object?

I have a Form which "listens" to events that are raised elsewhere (not on the Form itself, nor one of its child controls). Events are raised by objects which exist even after the Form is disposed, and may be raised in threads other than the one on which the Form handle was created, meaning I need to do an Invoke in the event handler (to show the change on the form, for example).

In the Dispose(bool) method of the form (overridden) I unsubscribed from all events that may still be subscribed when this method is called. However, Invoke is still called sometimes from one of the event handlers. I assume this is because the event handler gets called just a moment before the event is unsubscribed, then OS switches control to the dispose method which executes, and then returns control back to the handler which calls the Invoke method on a disposed object.

Locking the threads doesn't help because a call to Invoke will lock the calling thread until main thread processes the invoked method. This may never happen, because the main thread itself may be waiting for a release of the lock on the object that the Invoke-calling thread has taken, thus creating a deadlock.

So, in short, how do I correctly dispose of a Form, when it is subscribed to external events, which may be raised in different threads?

Here's how some key methods look at the moment. This approach is suffering the problems I described above, but I'm not sure how to correct them.

This is an event handler handling a change of Data part of the model:

private void updateData()
{
 if (model != null && model.Data != null)
 {
  model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);

  model.Data.SomeDataChanged += new MyEventHandler(updateSomeData);
 }
 updateSomeData();
}

This is an event handler which must make changes to the view:

private void updateSomeData()
{
 if (this.InvokeRequired) this.myInvoke(new MethodInvoker(updateSomeData));
 else
 {
  // do the necessary changes
 }
}

And the myInvoke method:

private object myInvoke(Delegate method)
{
 object res = null;
 lock (lockObject)
 {
  if (!this.IsDisposed) res = this.Invoke(method);
 }
 return res;
}

My override of the Dispose(bool) method:

protected override void Dispose(bool disposing)
{
 lock (lockObject)
 {
  if (disposing)
  {
   if (model != null)
   {
    if (model.Data != null)
    {
     model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);
    }
    // unsubscribe other events, omitted for brevity
   }
   if (components != null)
   {
    components.Dispose();
   }
  }
  base.Dispose(disposing);
 }
}

Update (as per Alan's request):

I never explicitly call the Dispose method, I let that be done by the framework. The deadlock has so far only happened when the application is closed. Before I did the locking I sometimes got some exceptions thrown when a form was simply closed.

like image 234
Nikola Novak Avatar asked Oct 23 '22 17:10

Nikola Novak


1 Answers

There are two approaches to consider. One is to have a locking object within the Form, and have the internal calls to Dispose and BeginInvoke calls occur within the lock; since neither Dispose nor BeginInvoke should take very long, code should never have to wait long for the lock.

The other approach is to just declare that because of design mistakes in Control.BeginInvoke/Form.BeginInvoke, those methods will sometimes throw an exception that cannot practically be prevented and should simply be swallowed in cases where it won't really matter whether or not the action occurs on a form which has been disposed anyway.

like image 146
supercat Avatar answered Oct 27 '22 11:10

supercat