William Gross
William Gross

Reputation: 2103

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.

Upvotes: 19

Views: 21696

Answers (4)

Rolf Kristensen
Rolf Kristensen

Reputation: 19857

Possible solution for protecting the callback method from working on a disposed timer:

ManualResetEvent waitHandle = new ManualResetEvent(false);
if (!timer.Dispose(waitHandle) || waitHandle.WaitOne((int)timeout.TotalMilliseconds)
{
    waitHandle.Close();  // Only close when not timeout
}

See also: https://stackoverflow.com/a/15902261/193178

Upvotes: 0

MrDoubleU
MrDoubleU

Reputation: 9

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;
}

Upvotes: 0

Addy
Addy

Reputation: 2522

You do not need to dispose of the timer to stop it. You can call Timer.Stop() or set Timer.Enabled to false, either of which will stop the timer from running.

Upvotes: -5

Henk Holterman
Henk Holterman

Reputation: 273179

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.

Upvotes: 12

Related Questions