跳至内容

WPF 中的定时器

2026-11-10

在此列举了一些,我个人觉得常用的定时器,不包含所有的定时器。

定时器运行上下文直接更新 UI回调重叠风险适用场景
DispatcherTimerUI 线程 (Dispatcher)可以无(但会阻塞 UI)轻量级 UI 刷新(时钟、状态)
System.Timers.Timer后台线程 (ThreadPool)不可以有后台复杂逻辑、耗时操作
PeriodicTimer异步等待 (await)不可以无(顺序处理)异步后台周期任务

DispatcherTimer

DispatcherTimer 是专为 WPF 设计的,它的 Tick 事件直接在 WPF 的 UI 线程(Dispatcher) 上执行。

这意味了在事件执行中,可以直接操作任何 UI 控件,而无需使用 Dispatcher.Invoke。因此非常适合一些轻量级的 UI 更新,比如时钟显示、进度条刷新或状态栏文字切换等。

using System.Windows.Threading;

// 简单示例:每秒更新一次时间显示
var timer = new DispatcherTimer
{
    Interval = TimeSpan.FromSeconds(1)
};
timer.Tick += (s, e) 
    => myTextBlock.Text = DateTime.Now.ToString("HH:mm:ss");
timer.Start();

Note

如果 Tick 事件中的处理逻辑耗时过长,会直接阻塞 UI 线程,导致界面卡顿。因此,它只适合轻量操作。

System.Timers.Timer

System.Timers.Timer 的 Elapsed 事件在后台线程上触发,这使得它摆脱了 UI 线程的束缚,适合执行一些较复杂的逻辑、耗时的操作,不会因此阻塞界面。

而代价是线程安全问题,在 Elapsed 中不能直接修改 UI 控件,必须通过 Dispatcher.Invoke 或 Dispatcher.InvokeAsync 将 UI 更新操作跳转到 UI 线程上执行。

using Timer = System.Timers.Timer;

var timer = new Timer(1000);
timer.Elapsed += (s, e) =>
{
    var data = DoHeavyWork(); // 后台耗时计算
    Application.Current.Dispatcher.Invoke(() =>
    {
        myTextBlock.Text = data; // UI 线程更新
    });
};
timer.Start();

Note

如果 Elapsed 的处理时间超过了 Interval,下一次回调可能会在前一次尚未完成时就开始,导致回调重叠。

PeriodicTimer

PeriodicTimer 是从 .NET 6 开始引入的异步友好型定时器,不再使用回调,而是通过 await WaitForNextTickAsync() 来等待下一个 Tick。

这种模式天然的避免了回调重叠的问题,因为下一次是等待上一次处理完成之后才开始,同时可以用 CancellationToken 优雅地取消循环。

using System.Threading;

async Task RunPeriodicAsync(CancellationToken ct)
{
    using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
    while (await timer.WaitForNextTickAsync(ct))
    {
        var data = await FetchDataAsync(ct); // 异步后台操作
        await Application.Current.Dispatcher.InvokeAsync(() =>
        {
            myTextBlock.Text = data; // 更新 UI
        });
    }
}
最后更新于