2024-05-23

Timers In C#

There are multiple types of Timers in C#. Today we will look at the major differences between two specific types of timers: Timer and PeriodicTimer. This was a recent issue one of my teams ran into.

System.Threading.Timer

This is a commonly used class for invoking a callback on a regular cadence. It works by instantiating the timer with a callback, and optional state, due time, and period. For the purposes of this article we'll focus on the due time and period as significant differences from the PeriodicTimer. The due time is the amount of time before the first tick and the period is how often the timer should tick. On each tick the callback will be invoked.

Timer timer = new Timer(TimerCallback, null, 0, 1000);
...
private static void TimerCallback(object? state)
{
    ConsoleExtensions.WriteLineGreen($"TimerCallback Starting: {Thread.CurrentThread.ManagedThreadId}: {DateTime.Now}");
    Thread.Sleep(5000);
    ConsoleExtensions.WriteLineRed($"TimerCallback Ending: {Thread.CurrentThread.ManagedThreadId}: {DateTime.Now}");
}

In this example we want the Timer to tick every 1 second, however, our callback will take 5 seconds to execute. When we run this, we'll see the following output:

Timer output

The Timer does not wait until the previous callback has finished to start the next invocation. This can lead to multiple concurrent instances of the callback running, which depending upon the scenario might be desirable or might not be desirable. Consider the scenario where the callback needs to run a database query. If that database query suddenly takes longer than expected to execute, then multiple concurrent instances of the callback could be stacking up all running a database query that takes longer than expected to execute. This could exacerbate the problem and use to precious database resources.

System.Threading.PeriodicTimer

Let's look at how the PeriodicTimer class differs. First off, the constructor only takes one argument: a TimeSpan for how often the timer should tick. Next we need to setup a while loop that awaits each next tick and then invokes the callback. This is a different setup than the Timer class. I've wrapped this in Task.Run in order to separate the while loop from the rest of my code so it is non-blocking.

PeriodicTimer periodicTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000));
Task.Run(async () =>
{
    while (await periodicTimer.WaitForNextTickAsync())
    {
        PeriodicTimerCallback();
    }
});
...
private static void PeriodicTimerCallback()
{
    ConsoleExtensions.WriteLineGreen($"PeriodicTimerCallback Starting: {Thread.CurrentThread.ManagedThreadId}: {DateTime.Now}");
    Thread.Sleep(5000);
    ConsoleExtensions.WriteLineRed($"PeriodicTimerCallback Ending: {Thread.CurrentThread.ManagedThreadId}: {DateTime.Now}");
}

The output of this is very different from the Timer class example:

Timer output

While we want our PeriodicTimer to tick every one second, because the callback takes longer than that to execute the PeriodicTimer waits until the callback is finished executing to invoke the next tick. By the time the callback finishes executing the PeriodicTimer knows it's past due for the previous tick so it executes immediately. Because it does not run invocations concurrently it can also re-use the same thread unlike the Timer class. In the scenario of a callback executing a database query that suddenly takes longer to execute than expected, this results in less pressure on the database since concurrent invocations aren't stacked on top of each other.

Summary

These two timer classes are very useful for scheduling recurring operations, but they operate very differently and can serve different purposes. Sometimes it is desired to have an operation be invoked on a specific time period every time. Other times it may not be desired to have overlapping executions of the timer callback.

Tags: Timers CSharp