Introducing PeriodicTimer, the new async timer from .NET 6

You often need to wait a specific interval of time between executions of a repeating task, but how to do this in .NET? You probably already did that in .NET but which Timer have you used?

.NET 5 has 5 different timers which have specific purposes:

.NET 6 came with a new type of timer, PeriodicTimer, that enables waiting asynchronously for timer ticks so we can use asynchronous methods with it. Moreover, this new timer allows us to avoid using callbacks which can lead to memory leaks in long-lived operations and can be the source of bugs which are difficult to investigate on.

PeriodicTimer has only one method, WaitForNextTickAsync, which waits for the next tick or for the timer to be stopped. And it has only one constructor, which takes a TimeSpan as parameter which is the duration of the tick. How wonderful is this?


PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000));

while (await timer.WaitForNextTickAsync())
{
    // Put your business logic here
}

Because PeriodicTimer is asynchronous, you can use asynchronous code too in the business logic. Another great feature of this timer is that he pauses when the business logic is running so you don't have to worry about overlapping problems (but you have to know that your code execution's duration impacts the ticks if you awaiting it, I mean if you have a timer that ticks all the seconds and a code that takes 5 seconds to run then the timer will ticks all the 6 seconds).

Let's see some usecases. Imagine you want a service to run a repeating task at a specific time interval.

Assume we have a background service for weather forecasting:


public class WeatherForecastBackgroundService: BackgroundService
{
    private readonly ILogger<WeatherForecastBackgroundService> logger;

    public WeatherForecastBackgroundService(ILogger<WeatherForecastBackgroundService> logger)
    {
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
    }
}

Which is registered as a hosted service:


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<WeatherForecastBackgroundService>();
var app = builder.Build();
app.Run();

How can we make this service executes the method several times at a specific interval?

We could use a task's delay:


public class WeatherForecastBackgroundService: BackgroundService
{
    private readonly ILogger<WeatherForecastBackgroundService> logger;

    public WeatherForecastBackgroundService(ILogger<WeatherForecastBackgroundService> logger)
    {
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
            await Task.Delay(1000, cancellationToken);
        }
    }
}

The output result would be:

This method works but notice that the delay between two method executions is not exactly one second as we asked for but a little more each time; it's because the method awaits for 1000 milliseconds after his execution so its execution time is added to the delay you want.

Can we do something to have a method that will be repeated at a specific interval, based on his starts and not his ends?

We could extract our method and not awaiting it but it could have catastrophic impacts on our application lifetime depending on his execution duration and his resources but it seems a pretty bad design. If we extract our behavior in a method and await the result the result would be close to our first one, with a little more delay because our code has now to handle a Task at each execution:


public class WeatherForecastBackgroundService: BackgroundService
{
    private readonly ILogger<WeatherForecastBackgroundService> logger;

    public WeatherForecastBackgroundService(ILogger<WeatherForecastBackgroundService> logger)
    {
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            await LogWeatherForecastAsync();
            await Task.Delay(1000, cancellationToken);
        }
    }

    private static async Task LogWeatherForecastAsync()
    {
        Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
    }
}

Let's take a look at the new timer added by .NET 6, PeriodicTimer, and use it in our code to see his behavior. We can add a PeriodicTimer as field in our class to handle the ticks and base our execution method on it using WaitForNextTickAsync method we saw earlier:


public class WeatherForecastBackgroundService: BackgroundService
{
    private readonly PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000));

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested
            && await timer.WaitForNextTickAsync(cancellationToken))
        {
            await LogWeatherForecastAsync();
        }
    }

    private static async Task LogWeatherForecastAsync()
    {
        Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
    }
}

The output is now something like that:

PeriodicTimer the new async timer from .NET 6 in a web application

As you can see the timer is extremely precise and the gap between each method execution is minimal.

Let's see the behavior if we add a delay of 500 milliseconds to the method to simulate a code execution:


private static async Task LogWeatherForecastAsync()
{
    Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
    await Task.Delay(500);
}
PeriodicTimer the new async timer from .NET 6 in a web application

The precision of the timer is not impacted by the method execution duration, the gap between each repetition is still to one second.

What will be the behavior of this code if the LogWeatherForecastAsync() takes more than one second to run and it still awaited:


private static async Task LogWeatherForecastAsync()
{
    Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
    await Task.Delay(2000);
}
PeriodicTimer the new async timer from .NET 6 in a web application

Now the gap between each execution is 2 seconds because it's the time that the method needs to execute, so the WaitForNextTickAsync method won't overlap several executions.

Using the PeriodicTimer in a console application

We just saw how to use the brand new PeriodTimer in a .NET 6 ASP.NET web application. The challenge is that the timer tick needs to be awaited but you don't want to block your code's flow for this. A way for doing this is to create a class that will have a PeriodicTimer, a Task and a CancellationTokenSource as members, an async method to run the code inside a loop that awaits the timer ticks, and a method to stop the timer using the CancellationTokenSource (you cancel the token, you wait for the next tick that will be the last one then you dispose the CancellationTokenSource that won't be needed anymore). The timer can starts in the constructor of the class or in a dedicated method. The code could be something like that:


internal class WeatherForecastBackgroundService
{
    private readonly PeriodicTimer timer;
    private readonly CancellationTokenSource cts = new();
    private Task? timerTask;

    public WeatherForecastBackgroundService(TimeSpan timerInterval)
    {
        timer = new(timerInterval);
    }

    public void Start()
    {
        timerTask = DoWorkAsync();
        Console.WriteLine("WeatherForecastBackgroundService just started");
    }

    private async Task DoWorkAsync()
    {
        try
        {
            while(await timer.WaitForNextTickAsync(cts.Token))
            {
                Console.WriteLine(string.Concat("WeatherForecastBackgroundService: ", DateTime.Now.ToString("O")));
                await Task.Delay(500);
            }
        }
        catch (OperationCanceledException)
        {

        }
    }

    public async Task StopAsync()
    {
        if(timerTask is null)
        {
            return;
        }

        cts.Cancel();
        await timerTask;
        cts.Dispose();
        Console.WriteLine("WeatherForecastBackgroundService just stopped");
    }
}

To call it in your program class you have just to instanciate a WeatherForecastBackgroundService with the interval you want in parameter then to start it and stop it when you want, it will execute in the background so it won't block your code's flow:


using PeriodicTimerLab.ConsoleApp;

Console.WriteLine("Hello to WeatherForecastBackgroundService");
Console.WriteLine("Press any button to start the service");
Console.ReadKey();

WeatherForecastBackgroundService weatherForecastBackgroundService = new (TimeSpan.FromMilliseconds(1000));
weatherForecastBackgroundService.Start();

Console.WriteLine("Press any button to stop the service");
Console.ReadKey();

await weatherForecastBackgroundService.StopAsync();
PeriodicTimer the new async timer from .NET 6 in a console application

As you can see it works and the timer tick is really really close to the second each time. This is a really modern, simple and nice way of implementing a timer, don't you think?

Notice that the PeriodicTimer is intended to be used only by a single consumer at a time: only one call to WaitForNextTickAsync(CancellationToken) may be in flight at any given moment. Also, the execution contest isn't captured.

For more informations you can read the official documentation and the links bellow.

June 28, 2022
  • .NET 6
  • .NET Timer
  • PeriodicTimer