Choosing Between .NET's Three Timers - PeriodicTimer/Timer/DispatcherTimer
· Go Komura · C#, .NET, WPF, Timer, Design
In the previous article, A Practical Guide to Achieving Soft Real-Time as Much as Possible on Ordinary Windows - The Checklist to Look at First, we covered avoiding Sleep-driven periodic loops in favor of event-driven approaches and waitable timers.
So what do you do in more everyday .NET app development?
The place where people get lost is PeriodicTimer, System.Threading.Timer, and DispatcherTimer.
They are all called timers, but their characters differ considerably:
- A timer whose ticks you
await - A timer whose callbacks arrive on the ThreadPool
- A timer that runs on the UI thread’s
Dispatcher
What tends to get mixed up in practice is roughly this.
- Passing an
asynclambda toSystem.Threading.Timereven though the periodic work is asynchronous - Touching the screen directly from a ThreadPool timer even though it is a WPF UI update
- Putting heavy work in a
DispatcherTimerand slowing down the whole screen - The previous “soft real-time” discussion and ordinary app periodic execution blurring together in your head
This article assumes mainly general C# / .NET apps on .NET 6 or later, and organizes
PeriodicTimer / System.Threading.Timer / DispatcherTimer in an order that makes everyday practice less confusing.
The intended targets are these.
- Workers / background services
- Console apps
- Behind-the-scenes processing in ASP.NET Core
- WPF desktop apps
By DispatcherTimer, this article mainly means WPF’s System.Windows.Threading.DispatcherTimer.
WinUI / UWP have a DispatcherTimer with the same idea.
For WinForms, it is more natural to look at System.Windows.Forms.Timer as the UI timer.
Note that what we deal with here is how to write periodic execution on the app side. When the accuracy of the period itself is the subject, we return to the soft real-time article.
Also, the code appearing in this article is published on GitHub as a complete buildable and runnable sample set (libraries and console demos for PeriodicTimer / System.Threading.Timer, plus unit tests verifying tick coalescing and callback overlap).
periodictimer-system-threading-timer-dispatchertimer-guide - komurasoft-blog-samples (GitHub)
Table of Contents
- The Conclusion First (In One Line)
- The One-Sheet Overview
- 2.1. The Big Picture
- 2.2. The First-Pass Decision Table
- What to Distinguish First
- 3.1. Callback Style, or Awaiting Ticks?
- 3.2. Does It Run on the ThreadPool, or on the UI Thread?
- 3.3. Periodic Processing and Precision Guarantees Are Separate Topics
- Typical Patterns
- 4.1. For Async Periodic Work:
PeriodicTimer - 4.2. For Light Callbacks on the ThreadPool:
System.Threading.Timer - 4.3. For WPF UI Updates:
DispatcherTimer - 4.4. For Soft-Real-Time-Leaning Periodic Work: Look at Other Tools
- 4.1. For Async Periodic Work:
- Common Anti-Patterns
- Code Review Checklist
- A Rough Decision Guide
- Summary
- References
1. The Conclusion First (In One Line)
- If you want to write fixed-interval work naturally on an
awaitbasis, start withPeriodicTimer - If you want to fire light callbacks periodically on the ThreadPool, use
System.Threading.Timer - If you want to update the screen on WPF’s UI thread, use
DispatcherTimer System.Threading.Timercallbacks can overlap. Sloppily cramming async work into it gets messy fastDispatcherTimerlets you touch the UI directly, but in exchange, heavy work in it easily stalls the UI itself- In the soft real-time context of the previous article, these three are not the protagonists of high-precision waiting
In short, the first three things to look at are:
- On which thread / context do you want it to run?
- Do you want to write the body sequentially with
async/await? - Can you tolerate overlapping callbacks?
Just separating these three makes things much less confusing.
2. The One-Sheet Overview
2.1. The Big Picture
flowchart LR
A["Want to do something at a fixed interval"] --> B{"Want it on the UI thread?"}
B -- "Yes" --> C["DispatcherTimer"]
B -- "No" --> D{"Want to write the body<br/>plainly with<br/>async / await?"}
D -- "Yes" --> E["PeriodicTimer"]
D -- "No" --> F{"Want to fire light<br/>callbacks on the<br/>ThreadPool?"}
F -- "Yes" --> G["System.Threading.Timer"]
F -- "No" --> H["Consider other designs<br/>Channel / BackgroundService / event / waitable timer"]
In practice, this branching is mostly sufficient. When in doubt, the least error-prone cut to make first is:
PeriodicTimer for async work, DispatcherTimer for UI updates.
System.Threading.Timer is handy, but with its callback overlap and lifetime-management quirks,
it is a bit temperamental as your very first choice.
2.2. The First-Pass Decision Table
| Situation | First choice | Where it runs | Why it fits | First caution |
|---|---|---|---|---|
| Run async work like HTTP / DB / file I/O at a fixed interval | PeriodicTimer |
Within the flow of your current async method | Writable on an await basis; stopping and cancellation are natural |
One timer, one consumer. Delays are not parallelized automatically |
| Run a light heartbeat / metrics emission / cache-expiry check on the ThreadPool | System.Threading.Timer |
ThreadPool | Lightweight, callback-style. Easy to fit into existing callback-based designs | Callbacks assume reentrancy. They can overlap. Hold a reference |
| Run a WPF clock display or light UI updates at a fixed interval | DispatcherTimer |
WPF’s Dispatcher (UI thread) |
Can touch the UI directly. Has priorities | Exact firing times are not guaranteed. Heavy work clogs the UI |
The accuracy of the period is the point, and you want to avoid Sleep-driven loops |
Don’t make these three the protagonists | - | The goal is not app-level periodic execution but designing wait precision | Look at events / waitable timers instead |
What matters in this table is to look at the execution location and the writing style, not the timer’s name. When timer selection goes wrong, the cause is more often not looking at “where it runs” than the API name.
3. What to Distinguish First
3.1. Callback Style, or Awaiting Ticks?
Separating this immediately improves visibility.
System.Threading.TimerandDispatcherTimerare callback / event stylePeriodicTimeris the style where youawaitthe next tick
That is:
- Callback style: “the timer calls you”
PeriodicTimer: “you wait for the next tick”
If the body is async and
you want to read “wait → process → wait again” as one continuous flow, PeriodicTimer is more natural.
Conversely, when:
- You want to fit into an existing callback-based design
- The body is short and synchronous
- You simply want a periodic kick
then System.Threading.Timer fits.
PeriodicTimer is convenient but not all-purpose.
It is not designed for multiple concurrent WaitForNextTickAsync calls on one timer,
and if multiple ticks occur while you are not waiting, they are coalesced into one.
It is important not to misread this as “it catches up automatically.”
3.2. Does It Run on the ThreadPool, or on the UI Thread?
The next thing to look at is where it executes.
System.Threading.Timer callbacks run on the ThreadPool, not on the creating thread.
This makes it suitable for background work, but it is not meant to touch the UI directly.
DispatcherTimer, by contrast, is a UI timer integrated into the Dispatcher queue.
In WPF, it runs on the same Dispatcher, so you can update the UI directly inside the Tick handler.
This difference is quite large.
- To touch the UI from a ThreadPool timer, you must explicitly marshal back to the UI
DispatcherTimermakes the UI easy to touch, but correspondingly consumes UI-thread time
In other words, DispatcherTimer’s strength is “safe access to the UI,” but
that simultaneously means “heavy work in it drags down input and rendering too.”
3.3. Periodic Processing and Precision Guarantees Are Separate Topics
This part matters as the connection to the previous article.
Even though the phrasing “do something at a fixed interval” is the same,
- Wanting periodic work every few seconds as an app-level convenience
- Wanting to get as close to a deadline as possible at the 1 ms to few-ms scale
are different problems.
System.Threading.Timer is a lightweight, easy-to-handle timer,
but it is not a dedicated tool for precision.
DispatcherTimer, too, is affected by the state of the Dispatcher queue and by priorities.
PeriodicTimer may look, from its name alone, like “the period must be tight,”
but its practical strength is the writability of the async flow, not precision.
So it is safer to separate at the outset whether you want to:
- Write app-level periodic execution, or
- Tighten wait precision
When these two blend, the timer-selection discussion gradually drifts somewhere strange.
4. Typical Patterns
4.1. For Async Periodic Work: PeriodicTimer
In a worker, a BackgroundService, or a resident console process,
if you want to run async work at a fixed interval, PeriodicTimer is the easiest to write with.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class CacheRefreshWorker : BackgroundService
{
private readonly ILogger<CacheRefreshWorker> _logger;
public CacheRefreshWorker(ILogger<CacheRefreshWorker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("CacheRefreshWorker started.");
await RefreshCacheAsync(stoppingToken);
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await RefreshCacheAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("CacheRefreshWorker stopping.");
}
}
private async Task RefreshCacheAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Refreshing cache...");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
The virtues of this shape:
- The code flow is easy to follow as a single
asyncmethod - The
CancellationTokenis easy to pass straight downstream - Less callback-based lifetime management and exception management
It is an especially good fit when the body is I/O-wait-centric:
- Calling HTTP
- Querying a DB
- Reading files
- Awaiting other async APIs
There are two cautions.
- Use it on a one-timer, one-consumer basis
- Decide yourself what the policy is when processing takes longer than the period
PeriodicTimer does not automatically parallelize to catch up just because the previous iteration ran long.
In that sense, it is a timer for “writing a fixed-interval async loop naturally.”
If you also care about testability, the constructor overload that accepts a TimeProvider is quietly convenient as well.
4.2. For Light Callbacks on the ThreadPool: System.Threading.Timer
If all you want is to invoke a short callback periodically, System.Threading.Timer is straightforward.
For example:
- Emitting a heartbeat
- Collecting light metrics
- Inserting a short expiry check
- Hanging off an existing callback-based design
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class HeartbeatService : IHostedService, IDisposable
{
private readonly ILogger<HeartbeatService> _logger;
private Timer? _timer;
private int _running;
public HeartbeatService(ILogger<HeartbeatService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void OnTimer(object? state)
{
if (Interlocked.Exchange(ref _running, 1) != 0)
{
return;
}
try
{
_logger.LogInformation("Heartbeat: {Now}", DateTimeOffset.Now);
}
finally
{
Volatile.Write(ref _running, 0);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
The reason this example includes Interlocked.Exchange is that
System.Threading.Timer does not wait for the previous callback to complete.
This part is quite important.
- Callbacks run on the ThreadPool
- Callbacks are reentrant by assumption
- If the work runs longer than the interval, callbacks can overlap
If the work is not light, it is calmer to design with:
- Skipping duplicate invocations
- Pushing onto a queue
- Moving to
PeriodicTimer
One more quietly important thing: hold a reference.
A System.Threading.Timer becomes eligible for GC once no reference remains, even while active.
Also, even right after calling Dispose(), callbacks already queued may still run afterward.
So System.Threading.Timer is:
- Lightweight
- Fast
- Simple
— but in exchange, it is a timer where you must properly shoulder the callback’s circumstances yourself.
4.3. For WPF UI Updates: DispatcherTimer
If you want to periodically refresh an on-screen clock or a light status display in WPF, DispatcherTimer is natural.
using System;
using System.Windows;
using System.Windows.Threading;
public partial class MainWindow : Window
{
private readonly DispatcherTimer _clockTimer;
public MainWindow()
{
InitializeComponent();
_clockTimer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromSeconds(1)
};
_clockTimer.Tick += ClockTimer_Tick;
_clockTimer.Start();
}
private void ClockTimer_Tick(object? sender, EventArgs e)
{
ClockText.Text = DateTime.Now.ToString("HH:mm:ss");
}
protected override void OnClosed(EventArgs e)
{
_clockTimer.Stop();
_clockTimer.Tick -= ClockTimer_Tick;
base.OnClosed(e);
}
}
The virtue of DispatcherTimer is that Tick is processed on WPF’s Dispatcher, so you can touch the UI directly.
This pairs well with, for example:
- Clock displays
- Light refresh of connection-status indicators
- Triggers for re-evaluating Commands
- Light updates of numbers shown on screen
But here too, there is a point where the mood changes.
DispatcherTimer runs on the UI thread, so
heavy work in the Tick handler drags down input, rendering, and layout along with it.
Also, DispatcherTimer is not a tool that guarantees “exactly at the specified time.”
It is affected by other work on the Dispatcher queue and by priorities.
So in practice, things stay stable if you keep in mind:
- Keep the Tick body light
- Offload heavy I/O and CPU work elsewhere
- On close, call
Stop()and unsubscribe, making the lifetime explicit
4.4. For Soft-Real-Time-Leaning Periodic Work: Look at Other Tools
This is the connection point to the previous article.
What the soft real-time article dealt with was not “running roughly every so many seconds is fine,” but how to reduce period jitter and deadline misses.
In that context, the themes are:
- Don’t rely on
Sleep-based relative waits - Use event-driven approaches and waitable timers
- Split the fast path and the slow path
- Measure the lateness
So it is cleanest to split the problem from the start:
- Everyday async periodic work in an app
→
PeriodicTimer - ThreadPool callbacks
→
System.Threading.Timer - UI updates
→
DispatcherTimer - The accuracy of the period itself is the protagonist → the world of the previous article
The question “I want to run as tightly as possible every 1 ms — which .NET timer is best?” is, about halfway, no longer a timer-selection question but a question of wait methods and design.
5. Common Anti-Patterns
5.1. Passing an async Lambda Straight to System.Threading.Timer
This one is very tempting.
_timer = new Timer(async _ => await RefreshAsync(), null,
TimeSpan.Zero, TimeSpan.FromSeconds(5));
It looks tidy, but TimerCallback is void.
So this async lambda is effectively treated like async void.
As a result:
- The caller cannot await it
- Completion cannot be waited on
- Exception management becomes difficult
- Callback overlap must additionally be considered
— a fairly swampy state of affairs.
If the body is async, considering PeriodicTimer first reads better.
5.2. Putting Heavy Work in a DispatcherTimer Tick
DispatcherTimer can touch the UI directly, so the temptation is to write everything there.
But that is the UI thread.
Put in:
- Long synchronous work
- Heavy CPU computation
- Blocking I/O
- Work containing long
awaits that can double-fire
and you collide head-on with UI input and rendering.
Keep the Tick body light, offload heavy work to the background, and bring only the needed results back to the UI — that is more stable.
5.3. Assuming PeriodicTimer Automatically Makes Up for Delays
This is also an easy misunderstanding.
PeriodicTimer is excellent as a tool for cleanly writing fixed-interval async loops, but
it does not run iterations in parallel on its own to catch up when the previous one runs long.
Since ticks occurring while you are not waiting can be coalesced into one, you must decide by design:
- Do you skip when late?
- Is only the latest state relevant?
- Or must every single iteration be processed?
5.4. Postponing Stop and Lifetime Management
Timers cause more accidents in stopping than in running.
The easy-to-miss items are these.
- Creating a
System.Threading.Timeras a local variable and not holding a reference - Leaving the
Dispose()story vague without stopping theSystem.Threading.Timer - Never calling
Stop()on aDispatcherTimer, never unsubscribing from Tick - The timer extending object lifetimes even after the screen has closed
DispatcherTimer in particular can keep alive the object its handler is bound to.
If you get that odd feeling of “huh, this Window should be closed but it’s still around,” this is where to look.
6. Code Review Checklist
- Can you explain whether the periodic work should be written as a UI update / ThreadPool callback / async loop?
- Is an async body being forced into a callback-style timer?
- If using
System.Threading.Timer, can the code tolerate overlapping callbacks, or is it guarded? - Does the
DispatcherTimerTick contain heavy work, blocking I/O, or long synchronous processing? - If using
PeriodicTimer, is the policy for falling behind decided? - Are the stop method (
Change/Dispose/Stop) and the app-shutdown flow clear? - Is the reference to the
System.Threading.Timerproperly held? - Is there unsubscription and cleanup for the
DispatcherTimerwhen the screen closes? - Has the problem been split, at the outset, into “app-level periodic execution” versus “wait precision”?
7. A Rough Decision Guide
Here are practical rules of thumb.
-
Hit an API every 30 seconds to refresh settings →
PeriodicTimer -
Send a heartbeat or light metrics every 5 seconds →
System.Threading.Timer -
Show a clock or light status updates in WPF →
DispatcherTimer -
Touch the UI directly on every tick →
DispatcherTimer -
The body of the periodic work is full of
awaits, and you want stopping and exceptions handled naturally →PeriodicTimer -
Add a small callback-based kick at low cost →
System.Threading.Timer -
Managing period precision and jitter at the 1-5 ms scale is the point → before these three, look at the wait methods in the previous article
Put very bluntly in one line each:
PeriodicTimeris the timer for asyncSystem.Threading.Timeris the timer for ThreadPool callbacksDispatcherTimeris the timer for the UI
With this mnemonic, you rarely miss by much.
8. Summary
What really matters in choosing a .NET timer is not the difference in names, but these three points.
- Where it runs
- In what flow you want to write it
- How you handle overlap and delays
As policy, this alone is enough to hold your own.
- Async periodic work:
PeriodicTimer - Light callbacks on the ThreadPool:
System.Threading.Timer - WPF UI updates:
DispatcherTimer - Precision as the protagonist: look at other wait methods
Timers blur together because the names are similar. But their roles are not that similar.
PeriodicTimeris a tool for shaping the async flowSystem.Threading.Timeris a tool for periodically kicking callbacksDispatcherTimeris a tool for periodic updates on the UI thread
Just thinking of these three separately makes the code considerably quieter.
Conversely, when they blend:
- What should be async becomes
async void-ish - The code crashes from touching the UI directly
- Callbacks overlap and the state gets muddy
- The period-precision discussion gets lumped in too
— fairly ordinary kinds of trouble ensue.
Start by looking at “where do you want it to run.” That alone makes timer selection a lot calmer.
9. References
- Full sample code for this article (library, demo, unit tests) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/periodictimer-system-threading-timer-dispatchertimer-guide
- Related article: A Practical Guide to Achieving Soft Real-Time as Much as Possible on Ordinary Windows - The Checklist to Look at First
- Related article: C# async/await Best Practices - The Decision Table to Look at First
- Related article: WPF/WinForms async and the UI Thread on One Sheet - Where await Returns, Dispatcher, ConfigureAwait, and Where .Result / .Wait() Get Stuck
- Timers - .NET
- PeriodicTimer Class
- PeriodicTimer.WaitForNextTickAsync(CancellationToken) Method
- PeriodicTimer.Dispose Method
- PeriodicTimer Constructor
- Timer Class (System.Threading)
- Timer Constructor (System.Threading)
- Background tasks with hosted services in ASP.NET Core
- DispatcherTimer Class (System.Windows.Threading)
- DispatcherTimer Class (Microsoft.UI.Xaml)
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Why Use the .NET Generic Host and BackgroundService in Desktop Apps
How to use the Generic Host and BackgroundService to organize startup, periodic processing, shutdown, logging, configuration, and DI in W...
Windows App Outsourcing and Contract Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or contract development, here is how to sort out existing software modification, device inte...
What Is the .NET Generic Host? - The Foundation for DI, Configuration, and Logging
The role of the Generic Host, explained through its relationship with DI, configuration, logging, IHostedService, and BackgroundService -...
What Is .NET Native AOT? - How It Differs from JIT and Trimming
What Native AOT is, explained through its differences from JIT, ReadyToRun, self-contained, single-file, trimming, and source generators ...
WPF/WinForms async and the UI Thread on One Sheet
Sorting out where execution resumes after await in WPF / WinForms, plus Dispatcher / Invoke, ConfigureAwait(false), and where .Result / ....
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
UI Threading & Timers
Topic page for WPF / WinForms UI threading, async flow, Dispatcher usage, and timer decisions.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
In Windows application development involving periodic execution, UI updates, and background processing, timer selection directly affects implementation quality.
Technical Consulting & Design Review
If you are at the stage of sorting out where to split the responsibilities of PeriodicTimer and DispatcherTimer, this can be explored as a technical consulting and design review engagement.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links