views:

168

answers:

2

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 ?

+4  A: 

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.

JaredPar
+1: Am I correct in thinking that for the second example, if we assume the only thread that is going to dispose of a UI object is the UI thread (which *should* be the case), you should never see an `ObjectDisposedException`, as the check for `control.IsDisposed` is carried out on the UI thread?
Alex Humphrey
That's not the only exception that can be raised, you'll get InvalidOperationException as well.
Hans Passant
@Hans, forgot about that one, added a note.
JaredPar
I've added your code in my form class, but for the overload of the 1st method i get an error at line : invoke.Invoke(del); because invoke can't take only one argument.
aleroot
@aleroot, fixed that issue.
JaredPar
+2  A: 

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.

Hans Passant
What is WF Timer ? is equal to DispatcherTimer ? I'm actually using System.Threading.Timer and not System.Timers.Timer.
aleroot
Windows Forms Timer. Yes, same thing as the WPF DispatcherTimer. I explained how to use a System.Threading.Timer, set the period to 0.
Hans Passant
Now i start the System.Threading.Timer thus : System.Threading.TimerCallback oCallbackGrid = new System.Threading.TimerCallback(GridTimer_Tick); timerRefreshGrid = new System.Threading.Timer(oCallbackGrid, null, 1000, 3000); because i need to fire GridTimer_Tick every 3 seconds. How can i do otherwise ?
aleroot
How can i run the GridTimer_Tick method every 3 seconds in another thread setting period to 0 ???
aleroot
Call the Change() method in the event handler to restart the timer. What does the callback do other than calling BeginInvoke?
Hans Passant
The callback query a database for get row to insert in a datatable, i need to run this callback every 3 seconds. What event handler i need to use for change the period ? Can you make me an example ?
aleroot
These kind of error (InvalidOperationException and ObjectDisposedException) happen sometimes only when i close the application, and the timer event try to update UI.
aleroot
But of course, it only bombs when the form is closed. Use this approach: http://stackoverflow.com/questions/1731384/how-to-stop-backgroundworker-on-forms-closing-event/1732361#1732361
Hans Passant