C# PeriodicTimer
Tags: csharp, dotnet, tutorial, softwaredevelopment
Published: 2024-05-26 14:29:30

## Introduction

A periodic timer enables waiting asynchronously for timer ticks. The main goal of this timer is to be used in a loop and to support async handlers.

Learn how to use a [PeriodicTimer](https://learn.microsoft.com/en-us/dotnet/api/system.threading.periodictimer?view=net-8.0) in a code sample that fires off a PeriodicTimer every 15 seconds, retrieves a random record from a SQLite Northwind database using [Dapper](https://github.com/DapperLib/Dapper) (EF Core would be overkill here).

PeriodicTimer was first release with NET6.

{% cta https://github.com/karenpayneoregon/csharp-11-ef-core-7-features/tree/master/PeriodicTimerApp %} WinForms Sample project {% endcta %}

{% cta https://github.com/karenpayneoregon/csharp-11-ef-core-7-features/tree/master/PeriodicTimerWebApp %} ASP.NET Core Sample project {% endcta %}

## Task
In a Windows Form project, click a button to start the timer using the following class.


![Windows form for code sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10dt1kz7uw00rmf7xrov.png)

## Main timer code

### Events

- **public static event OnShowTime OnShowTimeHandler** 
sends the current time to the form. The form subscribes to this event in the form constructor
- **public static event OnShowContact OnShowContactHandler** sends a random record reads from the SQLite contacts table using Dapper to the form which subscribes to the event in the form constructor.

### Local method

- IsQuarterMinute returns true is current time/seconds is a quarter of a minute.

In the code below a timer is created which triggers every second, each iteration of the while statement the time is sent to the form which displays the current time in a label.

If the current time is a quarter of a minute, read a random record and send the record to the form which is displayed in a label. This is followed by faking sending an email.

If there are any runtime exceptions they are written to a log file using [SeriLog](https://serilog.net/). In source project see folder SampleLogFile for samples of the error log file.

```csharp
public class TimerOperations
{
    public delegate void OnShowTime(string sender);
    public static event OnShowTime OnShowTimeHandler;

    public delegate void OnShowContact(Contacts sender);
    public static event OnShowContact OnShowContactHandler;

    /// <summary>
    /// Execute a data read operation every quarter minute to retrieve a random contact.
    /// </summary>
    public static async Task Execute(CancellationToken token)
    {
        static bool IsQuarterMinute()
        {
            var seconds = Now.Second;
            return seconds is 0 or 15 or 30 or 45;
        }

        using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));

        try
        {
            while (await timer.WaitForNextTickAsync(token) && !token.IsCancellationRequested)
            {
                
                OnShowTimeHandler?.Invoke($"Time {Now:hh:mm:ss}");

                if (IsQuarterMinute())
                {
                    Contacts contacts = DapperOperations.Contact();
                    OnShowContactHandler?.Invoke(contacts);
                    EmailOperations.SendEmail(contacts);
                }
            }
        }
        catch (OperationCanceledException) { }
        catch (Exception exception)
        {
            Log.Error(exception,"");
        }
    }
}
```

## Form code

**Start the timer**

The following starts the timer utilizing a cancellation token for providing a way to terminate the timer.

```csharp
private CancellationTokenSource cts = new();
.
.
.
private async void PeriodicTimerForm_Shown(object? sender, EventArgs e)
{
    await Start();
}
private async Task Start()
{
    StartButton.Enabled = false;
    ContactNameLabel.Text = "";

    if (cts.IsCancellationRequested)
    {
        cts.Dispose();
        cts = new CancellationTokenSource();
    }

    await TimerOperations.Execute(cts.Token);
}
```
> **Note**
> In the code above a check is needed to determine if the timer is currently running. If the timer is running (which it always will be since the initial firing off the timer is in form shown event), dispose of the cancellation token, create a new instance and start the processing again. 


**Stopping the timer**

```csharp
private void StopButton_Click(object sender, EventArgs e)
{
    cts.Cancel();
    StartButton.Enabled = true;
}
```

## Longer delay

In the above example the timer fired off every second which in some cases may be overkill. Let's change firing off every second to every 60 seconds.

This is done via `TimeSpan.FromMinutes(1)` and Task.Delay.

```csharp
public static async Task ExecuteWait(CancellationToken token)
{

    try
    {
        // take milliseconds into account to improve start-time accuracy
        var delay = (60 - UtcNow.Second) * 1000; 
        await Task.Delay(delay, token);

        using PeriodicTimer timer = new(TimeSpan.FromMinutes(1));

        while (await timer.WaitForNextTickAsync(token))
        {
            Contacts contacts = await DapperOperations.ContactAsync();
            OnShowContactHandler?.Invoke(contacts);
            EmailOperations.SendEmail(contacts);
        }
    }
    catch (OperationCanceledException) { }
    catch (Exception exception)
    {
        Log.Error(exception, "");
    }
}
```

## Important

In each of the code samples above `using` PeriodicTimer timer = new(...). Using ensure memory is properly disposed off when the application closes.

## Summary
Although code samples are done in a Windows Form the timer can be used in other type of project e.g. ASP.NET Core, Console etc.

Sample output from the web code sample.


![Console window for asp.net core code sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffh5nt1y6fsngpsgtvzn.png)

1.