Denis
Denis

Reputation: 12077

What is the proper way to Dispose of a class with a Timer?

Let's say I have a class which has a Timer object that doesn't do any critical work - just some GUI work. Let's say there are 2 scenarios where the timer elapses every 5 minutes:

  1. in the Timer_Elapsed delegate there is a lot of work that is done and it takes 2 minutes to complete.
  2. in the Timer_Elapsed delegate there is little work to be done and it takes a couple of milliseconds to complete

What is the proper way to dispose of the object & timer? Does the amount of time the Timer_Elapsed event delegate runs influence your decision on how to Dispose properly?

Upvotes: 3

Views: 4537

Answers (2)

Alex
Alex

Reputation: 13224

If, you need to stop your timer during disposal, and work could still be in progress in your timer delegate, that relies on shared resources, being disposed at the same time, you need to coordinate the "shutdown" process. The below snippet shows an example of doing this:

public class PeriodicTimerTask : IDisposable
{
    private readonly System.Timers.Timer _timer;
    private CancellationTokenSource _tokenSource;
    private readonly ManualResetEventSlim _callbackComplete;
    private readonly Action<CancellationToken> _userTask;

    public PeriodicTimerTask(TimeSpan interval, Action<CancellationToken> userTask)
    {
        _tokenSource = new CancellationTokenSource();
        _userTask = userTask;
        _callbackComplete = new ManualResetEventSlim(true);
        _timer = new System.Timers.Timer(interval.TotalMilliseconds);
    }

    public void Start()
    {
        if (_tokenSource != null)
        {
            _timer.Elapsed += (sender, e) => Tick();
            _timer.AutoReset = true;
            _timer.Start();
        }
    }

    public void Stop()
    {
        var tokenSource = Interlocked.Exchange(ref _tokenSource, null);
        if (tokenSource != null)
        {
            _timer.Stop();
            tokenSource.Cancel();
            _callbackComplete.Wait();
            _timer.Dispose();
            _callbackComplete.Dispose();
            tokenSource.Dispose();
        }
    }

    public void Dispose()
    {
        Stop();
        GC.SuppressFinalize(this);
    }

    private void Tick()
    {
        var tokenSource = _tokenSource;
        if (tokenSource != null && !tokenSource.IsCancellationRequested)
        {
            try
            {
                _callbackComplete.Wait(tokenSource.Token); // prevent multiple ticks.
                _callbackComplete.Reset();
                try
                {
                    tokenSource = _tokenSource;
                    if (tokenSource != null && !tokenSource.IsCancellationRequested)
                        _userTask(tokenSource.Token);
                }
                finally
                {
                    _callbackComplete.Set();
                }
            }
            catch (OperationCanceledException) { }
        }
    }
}

Usage example:

public static void Main(params string[] args)
{
    var periodic = new PeriodicTimerTask(TimeSpan.FromSeconds(1), cancel => {
        int n = 0;
        Console.Write("Tick ...");
        while (!cancel.IsCancellationRequested && n < 100000)
        {
            n++;
        }
        Console.WriteLine(" completed.");
    });
    periodic.Start();
    Console.WriteLine("Press <ENTER> to stop");
    Console.ReadLine();
    Console.WriteLine("Stopping");
    periodic.Dispose();
    Console.WriteLine("Stopped");
}

With output like below:

Press <ENTER> to stop
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.
Tick ... completed.

Stopping
Stopped

Upvotes: 6

Rogue
Rogue

Reputation: 676

There are multiple approaches to this, and like Alex said in the comments it depends on whether or not objects the delegate will be using are also disposed.

Let's say we have a "worst-case" scenario, in which the delegate does need to use objects which would be disposed.
A good way to handle this would be similar to a method the Process object has: WaitForExit(). This method would simply loop until it sees the delegate is done working (have a working bool which is set before and after the delegate runs?) then returns. Now you can have something like this in the code using that class:

// Time to shut down
myDisposable.WaitForFinish();
myDisposable.Dispose();

Thus we are essentially ensuring the delegate is done before disposing of it, stopping any sort of ObjectDisposedException.

Upvotes: 1

Related Questions