Reputation: 12077
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:
Timer_Elapsed
delegate there is a lot of work that is done and it takes 2 minutes to complete. Timer_Elapsed
delegate there is little work to be done and it takes a couple of milliseconds to completeWhat 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
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
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