If you have been coding in C# for a little while, you have likely run into a situation where you need to run a piece of code repeatedly—maybe polling an API, refreshing a cache, or sending a heartbeat signal.
You type “Timer” into your IDE, and suddenly you are bombarded with options:
System.Threading.TimerSystem.Timers.TimerPeriodicTimer
It can be overwhelming. Why are there so many? Which one should you pick?
In this guide, we will demystify these three common timers, look at how they work under the hood, and learn how to implement the modern standard using PeriodicTimer with graceful cancellation.
1. System.Threading.Timer: The Lightweight Workhorse
Think of System.Threading.Timer as the “bare metal” option. It has been around since the very beginning of .NET.
How it works
This is a simple timer that executes a callback method on a Thread Pool thread. When the timer expires, the system grabs a thread from the pool and runs your code.
using System.Threading;
public class LightweightExample
{
public void Start()
{
// Create a timer that waits 1 second, then repeats every 2 seconds
var timer = new Timer(CallbackMethod, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
Console.WriteLine("Press Enter to stop...");
Console.ReadLine();
// Clean up!
timer.Dispose();
}
private void CallbackMethod(object? state)
{
Console.WriteLine($"Task executed on thread: {Thread.CurrentThread.ManagedThreadId}");
}
}The “Gotcha”: Re-entrancy
This is crucial for beginners. If your timer is set to tick every 1 second, but your code takes 3 seconds to run, the timer won’t wait. It will trigger again on a different thread while the first one is still running.
Pros: Extremely lightweight and efficient.
Cons: Harder to use (requires callback delegates), doesn’t handle
asynccode well, and you must manually handle overlapping executions.
2. System.Timers.Timer: The Event-Based Wrapper
This is the timer most developers stumble upon first because it feels very familiar. It wraps System.Threading.Timer but adds a layer of “quality of life” features.
How it works
Instead of dealing with callbacks, this timer fires an Elapsed event. It’s easier to read but suffers from similar issues as the threading timer.
using System.Timers;
public class EventTimerExample
{
public void Start()
{
var timer = new Timer(2000); // 2 seconds
// Hook up the event
timer.Elapsed += OnTimedEvent;
timer.AutoReset = true; // Keep repeating
timer.Enabled = true; // Start the timer
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
Console.WriteLine($"The Event was raised at {e.SignalTime}");
}
}Pros: Easy to read syntax (Events), easy to start/stop.
Cons: Still suffers from re-entrancy (overlapping) issues, and exception handling inside events can be tricky.
3. PeriodicTimer: The Modern Hero (.NET 6+)
Introduced in .NET 6, PeriodicTimer was designed specifically to solve the problems of the previous two timers, particularly regarding Async/Await and Re-entrancy.
How it works
Instead of firing an event or a callback “blindly,” PeriodicTimer is designed to be used inside a loop. It waits for you to finish your work before ticking again.
public async Task RunPeriodicAsync()
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));
// This loop runs as long as the timer is ticking
while (await timer.WaitForNextTickAsync())
{
try
{
Console.WriteLine($"Doing work at {DateTime.Now}");
// Simulating work that takes time (e.g., calling an API)
await Task.Delay(1000);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}The “Gotcha”: It waits for you!
Notice the await timer.WaitForNextTickAsync(). This timer will not tick again until your code inside the loop finishes and loops back around. This effectively eliminates overlapping executions without you writing any extra code.

Join the conversation! Your thoughts help the community grow.