DEV Community

loading...

Timers in .NET

solrevdev profile image John Smith Originally published at solrevdev.com on ・2 min read

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 🎉

Discussion (5)

Collapse
katnel20 profile image
Katie Nelson

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.

Collapse
solrevdev profile image
John Smith Author

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...

Collapse
katnel20 profile image
Katie Nelson

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.

Thread Thread
solrevdev profile image
John Smith Author

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 and RunJobAsync could just be the one method.

I'll take another look later and will try and explain more in another blog post.

Thread Thread
katnel20 profile image
Katie Nelson

Great! Looking forward to it.

Forem Open with the Forem app