Replace Threading.Timer with a custom async timer class?
I am trying to replace the old Threading.Timer code with something that can handle an async function. The reason I am doing this is because you cannot pass Threading.Timer to an async function without making it an Async Sub which I have to understand is a bad idea.
The code I want to replace;
Dim SaveTimer as New Threading.Timer(AddressOf SaveToFile, Nothing, 1000 * 60 * 5, 1000 * 60 * 5)
SaveToFile is an asynchronous function, so I get "The task returned from this Async function will be removed and all exceptions in it will be ignored. Try changing it to Sub Async to propagate its exceptions."
The purpose of this timer is to save the contents of the class to a file every 5 minutes. However, I also sometimes call SaveToFile manually and if I want to reset the timer.
With Threading.Timer, I would do this:
Public Async Function ManuallySave As Tasks.Task
SaveTimer.Change(Timeout.Infinite, Timeout.Infinite)
Await SaveToFile
SaveTimer.Change(1000 * 60 * 5, 1000 * 60 * 5)
End Function
Now I know how to schedule something to be done with Tasks using Task.Delay, but I'm not sure how to stop and reset it.
Here's my attempt at an async timer class;
Public Class AsyncTimer
Private CancellationToken As New CancellationTokenSource
Private DelegateAction As Action(Of Tasks.Task) = Nothing
Public Sub New(DelegateAction As Action(Of Tasks.Task))
Me.DelegateAction = DelegateAction
End Sub
Public Async Function StartTimer(Interval As Integer, Optional Repeat As Boolean = False) As Tasks.Task
CancellationToken = New CancellationTokenSource
Do
Await Tasks.Task.Delay(Interval, CancellationToken.Token).ContinueWith(DelegateAction, CancellationToken.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Current)
Loop Until Not Repeat OrElse CancellationToken.IsCancellationRequested
End Function
Public Sub StopTimer()
CancellationToken.Cancel()
End Sub
End Class
As for me, I call the StopTimer and then shortly thereafter StartTimer, which replaces the CancellationToken with a new instance. I am worried that before canceling the token takes effect, I overwrite it.
What is the solution to this?
I know the code is in VB, but I also understand C #, so the answer would be accurate.
EDIT1: This should be thread safe. This is the cause of my concern.
source to share
Tick-ticks are queued in the thread pool. You can use async
it await
there as well. Make the tick function a function call async Task
and discard the resulting task. And document why.
void TimerProc(...) {
var task = TimerImpl();
//throw away task
}
async Task TimerImpl() {
try { MyCode(); }
catch (Exception ex) { Log(ex); }
}
Note that timer ticks can be delayed arbitrarily, they can run at the same time, and they can start after you have stopped the timer.
I don't understand why you need to use async code anyway. You are already in the thread pool, so you don't need to unlock the UI. Writing to local disks doesn't benefit much from asynchronous I / O. Perhaps the answer is: just use synchronous code here.
To protect it from being overwritten CancellationToken
, you need to copy it to all the locations it uses. Nothing should directly use the field.
Better yet, create a new instance AsyncTimer
instead of restarting it. Avoid mutating the condition.
source to share