Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object Disposed exception and multi thread application

I have an application that start System.Threading.Timer, then this timer every 5 seconds read some information from a linked database and update GUI on main form of application;

Since the System.Threading.Timer create another thread for the Tick event, i need to use Object.Invoke for updating User Interface on the main Form of application with code like this :

this.Invoke((MethodInvoker)delegate()
  {
       label1.Text = "Example";
  });

The app work very well, but sometimes when the user close the main form and then close the application, if the second thread on timer_tick event is updating the user interface on main thread the user get an ObjectDisposedException.

How can i do for stop and close the threading timer before closing the main form and avoiding then Object disposed exception ?

like image 612
aleroot Avatar asked Sep 04 '10 06:09

aleroot


2 Answers

This is a bit of a tricky proposition as you must ensure the following on a given Close event

  1. The timer is stopped. This is fairly straight forward
  2. The control being updated isn't disposed when the delegate is run. Again straight forward.
  3. The code currently running off of a timer tick has completed. This is harder but doable
  4. There are no pending Invoke methods. This is quite a bit harder to accomplish

I've run into this problem before and I've found that preventing this problem is very problematic and involves a lot of messy, hard to maintain code. It's much easier to instead catch the exceptions that can arise from this situation. Typically I do so by wrapping the Invoke method as follows

static void Invoke(ISynchronizedInvoke invoke, MethodInvoker del) {
  try {
    invoke.Invoke(del,null);
  } catch ( ObjectDisposedException ) {
    // Ignore.  Control is disposed cannot update the UI.
  }
}

There is nothing inherently wrong with ignoring this exception if you're comfortable with the consequences. That is if your comfortable with the UI not updating after it's already been disposed. I certainly am :)

The above doesn't take care of issue #2 though and it still needs to be done manually in your delegate. When working with WinForms I often use the following overload to remove that manual check as well.

static void InvokeControlUpdate(Control control, MethodInvoker del) {
  MethodInvoker wrapper = () => {
    if ( !control.IsDisposed ) {
      del();
    }
  };
  try {
    control.Invoke(wrapper,null);
  } catch ( ObjectDisposedException ) {
    // Ignore.  Control is disposed cannot update the UI.
  }
}

Note

As Hans noted ObjectDisposedException is not the only exception that can be raised from the Invoke method. There are several others, including at least InvalidOperationException that you need to consider handling.

like image 119
JaredPar Avatar answered Sep 17 '22 21:09

JaredPar


System.Timers.Timer is a horrible class. There is no good way to stop it reliably, there is always a race and you can't avoid it. The problem is that its Elapsed event gets raised from a threadpool thread. You cannot predict when that thread actually starts running. When you call the Stop() method, that thread may well have already been added to the thread pool but didn't get around to running yet. It is subject to both the Windows thread scheduler and the threadpool scheduler.

You can't even reliably solve it by arbitrarily delaying the closing of the window. The threadpool scheduler can delay the running of a thread by up to 125 seconds in the most extreme cases. You'll reduce the likelihood of an exception by delaying the close by a couple of seconds, it won't be zero. Delaying the close for 2 minutes isn't realistic.

Just don't use it. Either use System.Threading.Timer and make it a one-shot timer that you restart in the event handler. Or use a System.Windows.Forms.Timer, it is synchronous.

A WF Timer should be your choice here because you use Control.Invoke(). The delegate target won't start running until your UI thread goes idle. The exact same behavior you'll get from a WF timer.

like image 23
Hans Passant Avatar answered Sep 19 '22 21:09

Hans Passant