Timers and Threads in .Net

I recently came across the following ‘gotcha’ which once you know about it seems really obvious, but was driving me up the wall.

Side Note: Timers in .Net

In Visual Studio 2003 there are 2 timers availabe from the toolbox, System.Timers.Timer and System.Windows.Forms.Timer.

The Forms.Timer is the one that exhibits this problem (and is shown in the “Windows Forms” section of the toolbox), while the Timers.Timer does not (and it is shown in the “Components” section of the toolbox.

In Visual Studio 2005 the System.Timers.Timer also doesn’t exhibit this problem, but it is no longer available from the toolbox!

The Problem

I wanted to run some intensive code on a regular basis, without blocking the UI, i.e. starting it from a Timer on a different Thread. However the work could take a variable length of time so I wanted to stop the timer, and then start it again when the work was finished.

Imagine the following code in the Tick event of a timer:

private void timer1_Tick(object sender, EventArgs e)
{
  timer1.Stop();
  Timer1Label.Text = DateTime.Now.ToString();
  System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Timer1Worker));
  thread.Start();
}

and then the Timer1Worker method looks like this:

private void Timer1Worker()
{
  Random rand = new Random();
  System.Threading.Thread.Sleep(rand.Next(1, 10) * 1000);
  timer1.Start();
}

Well there will never be a second ‘Tick’ from the timer, even though interogating timer1 will reveal that the timer is enabled. The reason for this is to do (I assume) with threading. The timer is ‘attached’ to the UI thread – which is the reason you started the new thread in the first place, so the UI is not frozen during the worker method. However this new thread doesn’t seem to have the right kind of access to ‘kick’ the timer where it hurts when it wants to start it up again.

Why oh why doesn’t this throw an exception? I don’t know, but it doesn’t – it just silently fails.

The Solution

The following version of Timer1Worker fixes the problem. Notice the Invoke at the end of the method.

private void Timer1Worker()
{
  Random rand = new Random();
  System.Threading.Thread.Sleep(rand.Next(1, 10) * 1000);
  this.Invoke(new MethodInvoker(timer1.Start));
}

Another Solution (.Net 2.0)

An alternative would in .Net 2.0 is to use the Background Worker as so:

private void timer2_Tick(object sender, EventArgs e)
{
  timer2.Stop();
  Timer2Label.Text = DateTime.Now.ToString();
  backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
  Random rand = new Random();
  System.Threading.Thread.Sleep(rand.Next(1, 10) * 1000);
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
  timer2.Start();
}

The RunWorkerCompleted event is fired on the correct thread (i.e. the one that the RunWorkerAsync was fired from) and solves the problem.

[tags]programming,bug,.net[/tags]

3 thoughts on “Timers and Threads in .Net”

  1. I got hit with the same gotcha! A solution I came up with is to seed Random with Thread.CurrentThread.GetHashCode() or to give you the complete code…

    Random rand = new Random(Thread.CurrentThread.GetHashCode());

    This could potentially cause repeated ‘random’ sequences since its actually only pseudo-random. So consider if this could be a problem for your specific application.

  2. The problem I had wasn’t with the random number generation – thats just some boiler plate code to put some random delays in to illustrate the problem.

  3. I had that problem, too. I’m moving some code from Visual Studio 5 (’97) to VS2005. It’s really frustrating me how they decided to change how some timers work and such. It’s causing me to miss deadlines… I really wish I hadn’t been forced into upgrading compilers.

    When I kill the timer to try to prevent reentrancy, the timer never restarts. I’ve tried various things and none are working for me. It’s so frustrating to have production code that has worked for years now not work anymore because it was compiled with VS2005.

Comments are closed.