Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# System.Threading.Timer wait for dispose

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.

like image 253
Daniel Bişar Avatar asked Nov 15 '12 11:11

Daniel Bişar


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.


2 Answers

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();
}
like image 54
M Afifi Avatar answered Oct 21 '22 05:10

M Afifi


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;
            }
        }
    }
}
like image 30
BatteryBackupUnit Avatar answered Oct 21 '22 03:10

BatteryBackupUnit