A current C# project of mine required a timer where every couple of seconds a method would fire and a potentially fairly long-running process would run.
With .NET we have a few built-in options for timers:
System.Web.UI.Timer
Available in the .NET Framework 4.8 which performs asynchronous or synchronous Web page postbacks at a defined interval and was used back in the older WebForms days.
System.Windows.Forms.Timer
This timer is optimized for use in Windows Forms applications and must be used in a window.
System.Timers.Timer
Generates an event after a set interval, with an option to generate recurring events. This timer is almost what I need however this has quite a few stackoverflow posts where exceptions get swallowed.
System.Threading.Timer
Provides a mechanism for executing a method on a thread pool thread at specified intervals and is the one I decided to go with.
Issues
I came across a couple of minor issues the first being that even though I held a reference to my Timer object in my class and disposed of it in a Dispose method the timer would stop ticking after a while suggesting that the garbage collector was sweeping up and removing it.
My Dispose method looks like the first method below and I suspect it is because I am using the conditional access shortcut feature from C# 6 rather than explicitly checking for null first.
public void Dispose()
{
// conditional access shortcut
_timer?.Dispose();
}
public void Dispose()
{
// null check
if(_timer != null)
{
_timer.Dispose();
}
}
A workaround is to tell the garbage collector to not collect this reference by using this line of code in timer’s elapsed method.
GC.KeepAlive(_timer);
The next issue was that my TimerTick event would fire and before the method that was being called could finish another tick event would fire.
This required a stackoverflow search where the following code fixed my issue.
// private field
private readonly object _locker = new object();
// this in TimerTick event
if (Monitor.TryEnter(_locker))
{
try
{
// do long running work here
DoWork();
}
finally
{
Monitor.Exit(_locker);
}
}
And so with these two fixes in place, my timer work was behaving as expected.
Solution
Here is a sample class with the above code all in context for future reference
Success 🎉
Top comments (5)
How would you handle a cancellation token? I have a need for a timer that should stop firing on cancellation and also the DoWork method should immediately quit.
Hi Katie,
Take a look at this. hitting ctrl-c while its running or in the 5-second DoWork method will cancel gracefully and hopefully do what you want.
I plan to blog about it in more detail later but this should help.
Gist here
gist.github.com/solrevdev/f28ab813...
Thanks John. I see now the running timer callback is buried several levels down. Makes things a bit more complex, but I understand what you did.
Ahh yes, the timer's TimerCallback method signature needed to be
void DoWork(object state)
hence needing another method call that uses async Task accepting a CancellationToken.But looking at it again I think that
DoWorkAsync
andRunJobAsync
could just be the one method.I'll take another look later and will try and explain more in another blog post.
Great! Looking forward to it.