7

I have a class which uses a Timer. This class implements IDispose. I would like to wait in the Dispose method until the timer will not fire again.

I implement it like this:

private void TimerElapsed(object state)
{
    // do not execute the callback if one callback is still executing
    if (Interlocked.Exchange(ref _timerIsExecuting, 1) == 1) 
        return;

    try
    {
        _callback();
    }
    finally
    {
        Interlocked.Exchange(ref _timerIsExecuting, 0);
    }
}

public void Dispose()
{
    if (Interlocked.Exchange(ref _isDisposing, 1) == 1)
        return;

    _timer.Dispose();

    // wait until the callback is not executing anymore, if it was
    while (_timerIsExecuting == 0) 
    { }

    _callback = null;
}

Is this implementation correct? I think it mainly depends on the question if _timerIsExecuting == 0 is an atomic operation. Or would I have to use a WaitHandle. For me it seems it would make the code unnecessarily complicated...

I am not an expert in multi-threading, so would be happy about any advice.

Patrick D'Souza
  • 3,491
  • 2
  • 22
  • 39
Daniel Bişar
  • 2,663
  • 7
  • 32
  • 54
  • I am developing an ASP.NET application. The timer is disposed on the call of Dispose of the HttpApplication. The reason: A callback could access the logging system. So i have to assure the before disposing the logging system the timer is disposed. – Daniel Bişar Nov 15 '12 at 11:29
  • @LMB Surely if a method has an `IDisposable` you dispose of it. Yes the `GC` can clean up, but if this is sufficient and efficient why have an `IDisposable`? – M Afifi Nov 15 '12 at 12:33
  • @MAfifi For when you use unmannaged resources. – LMB Nov 15 '12 at 12:38
  • See also: https://stackoverflow.com/questions/6379541/reliably-stop-system-threading-timer/15902261#15902261 – Rolf Kristensen Jan 25 '22 at 04:20

3 Answers3

10

Unless you have a reason not to use System.Threading.Timer This has a Dispose method with a wait handle

And you can do something like,

private readonly Timer Timer;
private readonly ManualResetEvent TimerDisposed;
public Constructor()
{
    Timer = ....;
    TimerDisposed = new ManualResetEvent(false);
}

public void Dispose()
{
    Timer.Dispose(TimerDisposed);
    TimerDisposed.WaitOne();
    TimerDisposed.Dispose();
}
M Afifi
  • 4,645
  • 2
  • 28
  • 48
  • 2
    There's two issues with this code: doesn't support multiple disposal (see [here](https://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx#Anchor_1)) plus there's a [documented](https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2) race condition where an `ObjectDisposedException` can occur during disposal - this should be mentioned. – BatteryBackupUnit Feb 19 '16 at 17:31
6

Generally one can use the Timer.Dispose(WaitHandle) method, but there's a few pitfalls:

Pitfalls

  • Support for multiple-disposal (see here)

If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times. Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed.

  • Timer.Dispose(WaitHandle) can return false. It does so in case it's already been disposed (i had to look at the source code). In that case it won't set the WaitHandle - so don't wait on it! (Note: multiple disposal should be supported)

  • not handling a WaitHandle timeout. Seriously - what are you waiting for in case you're not interested in a timeout?
  • Concurrency issue as mentioned here on msdn where an ObjectDisposedException can occur during (not after) disposal.
  • Timer.Dispose(WaitHandle) does not work properly with -Slim waithandles, or not as one would expect. For example, the following does not work (it blocks forever):
 using(var manualResetEventSlim = new ManualResetEventSlim)
 {
     timer.Dispose(manualResetEventSlim.WaitHandle);
     manualResetEventSlim.Wait();
 }

Solution

Well the title is a bit "bold" i guess, but below is my attempt to deal with the issue - a wrapper which handles double-disposal, timeouts, and ObjectDisposedException. It does not provide all of the methods on Timer though - but feel free to add them.

internal class Timer
{
    private readonly TimeSpan _disposalTimeout;

    private readonly System.Threading.Timer _timer;

    private bool _disposeEnded;

    public Timer(TimeSpan disposalTimeout)
    {
        _disposalTimeout = disposalTimeout;
        _timer = new System.Threading.Timer(HandleTimerElapsed);
    }

    public event Signal Elapsed;

    public void TriggerOnceIn(TimeSpan time)
    {
        try
        {
            _timer.Change(time, Timeout.InfiniteTimeSpan);
        }
        catch (ObjectDisposedException)
        {
            // race condition with Dispose can cause trigger to be called when underlying
            // timer is being disposed - and a change will fail in this case.
            // see 
            // https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
            if (_disposeEnded)
            {
                // we still want to throw the exception in case someone really tries
                // to change the timer after disposal has finished
                // of course there's a slight race condition here where we might not
                // throw even though disposal is already done.
                // since the offending code would most likely already be "failing"
                // unreliably i personally can live with increasing the
                // "unreliable failure" time-window slightly
                throw;
            }
        }
    }

    private void HandleTimerElapsed(object state)
    {
        Elapsed.SafeInvoke();
    }

    public void Dispose()
    {
        using (var waitHandle = new ManualResetEvent(false))
        {
            // returns false on second dispose
            if (_timer.Dispose(waitHandle))
            {
                if (!waitHandle.WaitOne(_disposalTimeout))
                {
                    throw new TimeoutException(
                        "Timeout waiting for timer to stop. (...)");
                }
                _disposeEnded = true;
            }
        }
    }
}
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
-1

Why you need to dispose the Timer manually? Isn't there any other solution. As a rule of thumb, you're better leaving this job to GAC. – LMB 56 mins ago

I am developing an ASP.NET application. The timer is disposed on the call of Dispose of the HttpApplication. The reason: A callback could access the logging system. So i have to assure the before disposing the logging system the timer is disposed. – SACO 50 mins ago

It looks like you have a Producer/Consumer pattern, using the timer as Porducer.

What I'd do in this case, would be to create a ConcurrentQueue() and make the timer enqueue jobs to the queue. And then, use another safe thread to read and execute the jobs.

This would prevent a job from overlapping another, which seems to be a requirement in your code, and also solve the timer disposing problem, since you could yourQueue == null before adding jobs.

This is the best design.

Another simple, but not robust, solution, is running the callbacks in a try block. I don't recommend to dispose the Timer manually.

Community
  • 1
  • 1
LMB
  • 1,137
  • 7
  • 23
  • 1
    Things like timers are very hard for the GC to handle. The problem is that if e.g. a timer event serves only to do something to some object "George" every second, the timer will be useful as long as anyone is interested in George, and will cease to be useful once all references to George by potentially-interested entities are abandoned. One may be able to handle the situation by having a timer hold a `WeakReference` to George, and dispose itself if the weak reference dies; such an approach may use the GC, but is hardly "leaving the job to the GC". – supercat May 03 '13 at 15:18