I have made a timer class:
- The timer should call an async method and wait between two calls.
- I want to wake up the timer to skip the wait period.
- When I dispose the timer from outside the timer I want to wait until the current call is completed.
- I want to stop the timer from inside the timer.
public sealed class CompletionTimer : IDisposable
{
private readonly CancellationTokenSource disposeToken = new CancellationTokenSource();
private readonly Task runTask;
private int requiresAtLeastOne;
private CancellationTokenSource wakeupToken;
public CompletionTimer(int delayInMs, Func<CancellationToken, Task> callback, int initialDelay = 0)
{
runTask = RunInternal(delayInMs, initialDelay, callback);
}
public void Dispose()
{
disposeToken.Cancel();
runTask.Wait();
}
public void Wakeup()
{
ThrowIfDisposed();
Interlocked.CompareExchange(ref requiresAtLeastOne, 2, 0);
wakeupToken?.Cancel();
}
private async Task RunInternal(int delay, int initialDelay, Func<CancellationToken, Task> callback)
{
if (initialDelay > 0)
{
await WaitAsync(initialDelay).ConfigureAwait(false);
}
while (requiresAtLeastOne == 2 || !disposeToken.IsCancellationRequested)
{
try
{
await callback(disposeToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
finally
{
requiresAtLeastOne = 1;
}
await WaitAsync(delay).ConfigureAwait(false);
}
}
private async Task WaitAsync(int intervall)
{
try
{
wakeupToken = new CancellationTokenSource();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(disposeToken.Token, wakeupToken.Token))
{
await Task.Delay(intervall, cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
}
}
The requiresAtLeastOne
field is to enforce at least one run when I do something like that (in a test or so):
var timer = new CompletionTimer(10000, ct => ... );
timer.Wakeup();
timer.Dispose();
It almost works perfectly, but one time (of thousand of calls) I had a deadlock in the Dispose
method, when I was calling it from within a timer. I am more confused why it does not deadlock all the time.