Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I gracefully stop a System.Threading.Timer?

I have a Windows Service implemented in C# that needs to do some work every so often. I've implemented this using a System.Threading.Timer with a callback method that is responsible for scheduling the next callback. I am having trouble gracefully stopping (i.e. disposing) the timer. Here's some simplified code you can run in a console app that illustrates my problem:

const int tickInterval = 1000; // one second

timer = new Timer( state => {
                       // simulate some work that takes ten seconds
                       Thread.Sleep( tickInterval * 10 );

                       // when the work is done, schedule the next callback in one second
                       timer.Change( tickInterval, Timeout.Infinite );
                   },
                   null,
                   tickInterval, // first callback in one second
                   Timeout.Infinite );

// simulate the Windows Service happily running for a while before the user tells it to stop
Thread.Sleep( tickInterval * 3 );

// try to gracefully dispose the timer while a callback is in progress
var waitHandle = new ManualResetEvent( false );
timer.Dispose( waitHandle );
waitHandle.WaitOne();

The problem is that I get an ObjectDisposedException from timer.Change on the callback thread while waitHandle.WaitOne is blocking. What am I doing wrong?

The documentation for the Dispose overload I'm using says:

The timer is not disposed until all currently queued callbacks have completed.

Edit: It appears that this statement from the documentation may be incorrect. Can someone verify?

I know that I could work around the problem by adding some signaling between the callback and the disposal code as Henk Holterman suggested below, but I don't want to do this unless absolutely necessary.

like image 309
William Gross Avatar asked Sep 10 '12 15:09

William Gross


People also ask

How do you stop a timer thread?

To end or quit the timer, one must use a cancel() function. Importing the threading class is necessary for one to use the threading class. The calling thread can be suspended for seconds using the function time. sleep(secs).

Is system threading timer thread safe?

Timer is not thread-safe.

What is System threading timer?

System.Threading. Timer Class. The Timer class (in the System. Threading namespace) is effective to periodically run a task on a separate thread. It provides a way to execute methods at specified intervals.

Does system timers timer run in a separate thread?

Timer raises the elapsed event, is it raised in an independent thread? Yes, they run in a different thread.


2 Answers

With this code

 timer = new Timer( state => {
                   // simulate some work that takes ten seconds
                   Thread.Sleep( tickInterval * 10 );

                   // when the work is done, schedule the next callback in one second
                   timer.Change( tickInterval, Timeout.Infinite );
               },
               null,
               tickInterval, // first callback in one second
               Timeout.Infinite );

it is almost certain that you will Dispose the timer while it is sleeping.

You will have to safeguard the code after Sleep() to detect a Disposed timer. Since there is no IsDisposed property a quick and dirty static bool stopping = false; might do the trick.

like image 163
Henk Holterman Avatar answered Oct 17 '22 00:10

Henk Holterman


As described in "Concurrent Programming on Windows":
Create a dummy class InvalidWaitHandle, inheriting from WaitHandle:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Threading;

namespace MyNameSpace
{
    class InvalidWaitHandle : WaitHandle
    {

    }
}

Hence you can Dispose a System.Threading.Timer properly like this:

public static void DisposeTimer()
{
   MyTimer.Dispose(new InvalidWaitHandle());
   MyTimer = null;
}
like image 41
MrDoubleU Avatar answered Oct 17 '22 01:10

MrDoubleU