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 do you do this in .NET? You have probably already done that in .NET, but which Timer did you use?

.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 that are difficult to investigate.

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 a parameter, which is the duration of the tick. How wonderful is that?


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

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

Because PeriodicTimer is asynchronous, you can use asynchronous code in the business logic too. Another great feature of this timer is that it 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 await it; I mean, if you have a timer that ticks every second and code that takes 5 seconds to run, then the timer will tick every 6 seconds).

Let's see some use cases. 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 execute the method several times at a specific interval?

We could use a task 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 waits for 1000 milliseconds after its 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 its starts and not its ends?

We could extract our method and not await it, but that could have catastrophic impacts on our application lifetime depending on its execution duration and its resources, and it seems a pretty bad design. If we extract our behavior into a method and await the result, the result would be close to our first one, with a little more delay because our code now has 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 its behavior. We can add a PeriodicTimer as a field in our class to handle the ticks and base our execution method on it, using the 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 this:

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

What will the behavior of this code be if LogWeatherForecastAsync() takes more than one second to run and is 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 PeriodicTimer 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. One way of 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 which will be the last one, then you dispose the CancellationTokenSource that won't be needed anymore). The timer can start in the constructor of the class or in a dedicated method. The code could be something like this:


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 just have to instantiate a WeatherForecastBackgroundService with the interval you want as a parameter, then 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 context isn't captured.

For more information you can read the official documentation and the links below.

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