I'm using System.Threading.Timer
to run some things that are async
It means my callbacks are async void
.
private async void CallbackAsync(object state) {
_timer.Change(Timeout.Infinite, Timeout.Infinite);
await SomethingAsync();
_timer.Change(TimeToNextCallback, Timeout.Inifinite);
}
With non async callbacks I have used code like this to shut down the timer safely.
using (var waitHandle = new AutoResetEvent(false)) {
if (_timer.Dispose(waitHandle)) {
waitHandle.WaitOne();
}
}
My understanding is that this will not work with async callbacks. The callback will return on the first await
and the Timer is unaware of scheduled continations. I.e the code above might return before the continuations has completed (and the callback might try to change a disposed timer).
First, is my understanding correct?
Second, what is the correct way to wait for async callbacks to finish?
Your understanding is correct that your Timer
will be unaware that your CallbackAsync
function is still running and thus your call to Dispose(WaitHandle)
will return and signal your WaitHandle
immediately, at which point a call to CallbackAsync
may make a call to Timer.Change
after the Timer
has been disposed.
There are a few red flags here:
async void
. This is almost always a bad idea because async functions should return a Task, otherwise there is nothing for a caller to await.Thread
and Task
paradigms.Because TimerCallback
does not support Tasks, the only way to make this work correctly while still using Timer
would be to make your callback synchronous by dropping the async/await
keywords and using Task.Wait
to block until the Task
returned by SomethingAsync
is complete. As others have mentioned it would also be wise to add support for cancellation by creating a CancellationToken
and passing it to SomethingAsync
, that way when you call Dispose
you will have the ability to cancel a running callback (this CancellationToken
will need to be passed by SomethingAsync
to any other async functions it calls and/or periodically checked for cancellation as SomethingAsync
is executing).
EDIT: If you want to shift to a more natural Task
based way of doing this see Run async method regularly with specified interval