C# PeriodicTimer
Karen Payne
Posted on May 26, 2024
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 in a code sample that fires off a PeriodicTimer every 15 seconds, retrieves a random record from a SQLite Northwind database using Dapper (EF Core would be overkill here).
PeriodicTimer was first release with NET6.
Task
In a Windows Form project, click a button to start the timer using the following class.
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. In source project see folder SampleLogFile for samples of the error log file.
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.
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
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.
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.
Posted on May 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.