4

My System.Threading.Timer (which has a callback) never fires reliably. This is part of my programming assignment where I input the amount of time the timer is supposed to run from a textbox.

The timer is declared like this:

System.Threading.Timer timer = new System.Threading.Timer(WorkerObject.callback, null, delay, Timeout.Infinite);

And the delay is the simply an int describing the delay for the callback to fire the first time (it is only supposed to fire once).

The callback method is like this:

 public static void callback(Object stateinfo)
 {
     stop = true;
 }

And all that does is set a flag to true which stops a loop (which is being run by a thread on a ThreadPool, in effect, stopping the thread).

The loop looks like this:

while (!stop)
{
    currentTextbox.Invoke(new Action(delegate()
    {
        currentTextbox.AppendText((counter++) + Environment.NewLine);
        currentTextbox.Update();
     }));
}

My problem is that the stop variable is always false for any delay over 5000 milliseconds. Is there a way to "force" the callback to always fire?

docaholic
  • 613
  • 9
  • 29
  • This question might not apply (I forget the API offhand)... but do you start the timer? – Jaxidian Sep 23 '13 at 21:47
  • The program is just printing out a counter which is continually incremented until the timer says stop. System.Threading.Timer doesn't need to be started; it starts automatically after a delay (0 for no delay, but in my case, i specified a delay) – docaholic Sep 23 '13 at 21:52
  • @Jaxidian: `System.Threading.Timer` starts when you create it. – Jim Mischel Sep 23 '13 at 22:00

3 Answers3

12

You need to hold on to the reference to the timer.

Most likely the timer object is being garbage collected, which will run its finalizer, stopping the timer.

So hold on to the reference for as long as you need the timer to be alive.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 1
    +1. This is an insidious problem. If you run the code in the debugger (in either Debug or Release mode), the timer won't be collected. But if you run it without the debugger attached, the timer will get collected. Made me *crazy* the first time I ran into that one. – Jim Mischel Sep 23 '13 at 22:11
  • 1
    @JimMischel: I totally agree. My memorable moment was passing a delegate to an unmanaged API. With the debugger attached everything worked fine, but without the debugger the GC got a lot more aggressive. I suppose we all have our stories :) – Brian Gideon Sep 24 '13 at 00:56
  • Yup, this was exactly it! @JimMischel , I had the same problem, in the debugger, the timer would work correctly, but otherwise the timer would get garbage collected. – docaholic Sep 24 '13 at 04:02
5

I would suggest using a CancellationTokenSource:

static CancellationTokenSource Cancel = new CancellationTokenSource();

public static void Callback(object state)
{
    Cancel.Cancel();
}

and your loop:

while (!Cancel.IsCancellationRequested)
{
    ...
}

This is much cleaner than using volatile, and is easier to port when you move your simple proof of concept to separate classes. See my blog, Polling for Cancellation, for more info.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
1

The runtime Jitter is probably optimizing away your while(!stop) condition to while(true). Mark the stop variable as volatile.

private volatile bool stop = false;
YK1
  • 7,327
  • 1
  • 21
  • 28