Timers in .NET
27 Jan 2020A 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 🎉