periodictimer不是开箱即用的定时器,仅提供异步等待tick能力,必须在while循环内调用waitfornexttickasync才能实现轮询;它不自动执行逻辑,周期从上一次等待完成起算,天然防堆积,但要求.net 6+且需正确disposeasync。

PeriodicTimer 不是“开箱即用”的定时器,它不自动执行任何逻辑,只提供异步等待下一次 tick 的能力;直接拿它当 Timer 用会漏掉所有业务代码。
PeriodicTimer.WaitForNextTickAsync 必须在循环体内调用
常见错误现象:await timer.WaitForNextTickAsync() 写在 while 外,结果只等一次就退出,轮询彻底失效。
正确做法是把等待和工作逻辑都放进 while 循环里,每次 tick 到达后才执行业务:
- 每次
WaitForNextTickAsync()返回true,表示定时器仍活跃,可继续下一轮 - 若返回
false(如被DisposeAsync()或取消),循环自然终止 - 不要提前
await启动——它本身不启动计时,只是“第一次等待”
PeriodicTimer 不等于“固定间隔发请求”,而是“上一次完成后再等下一次”
典型错误:把 TimeSpan.FromSeconds(5) 理解成“每 5 秒强制触发一次”,结果在 DoPollingWorkAsync() 耗时 8 秒时,请求堆积或并发失控。
它实际含义是:“从上一次 WaitForNextTickAsync() 完成起,再等 5 秒,才允许你进入下一轮”。所以轮询节奏由业务耗时 + 周期共同决定:
- 若业务执行快(2 秒),则每 5 秒 tick 一次,空闲 3 秒
- 若业务执行慢(8 秒),则下一轮要等满 8 + 5 = 13 秒后才开始
- 这种“节拍对齐”机制天然防堆积,但需明确这不是“硬实时调度”
.NET 6+ 才有 PeriodicTimer,老项目升级前先确认框架版本
错误信息:The type or namespace name 'PeriodicTimer' could not be found,大概率是目标框架低于 .NET 6。
它位于 System.Threading 命名空间,无需 NuGet 包,但以下情况不兼容:
- .NET Core 3.1、.NET 5 或更早版本 —— 没有该类型
- Unity(非 .NET 6+ IL2CPP 构建)—— 运行时缺失实现
- 某些 AOT 编译场景(如 iOS)—— 需验证
ValueTask支持是否完整
替代方案不是“降级用 Timer + Task.Run”,而是改用 Task.Delay 手写循环(注意取消和异常传播)。
DisposeAsync() 必须 await,且不能省略 finally 块
常见坑:只调 timer.Dispose()(同步版),或漏掉 await,导致后续 WaitForNextTickAsync() 可能抛 ObjectDisposedException,或资源未释放。
DisposeAsync() 是真正异步清理,它会立即让挂起的 WaitForNextTickAsync() 返回 false,并释放底层 IO 定时器句柄:
- 必须放在
finally块中,确保无论成功/异常/取消都执行 - 不能在
using语句中直接await——using不支持异步 dispose,得手动写try/finally - 若外层有
CancellationToken,建议传给WaitForNextTickAsync(ct),而非依赖DisposeAsync()触发退出
最容易被忽略的一点:PeriodicTimer 的周期精度依赖系统时钟分辨率,Windows 默认约 15ms,Linux 取决于 CLOCK_MONOTONIC;如果你需要 sub-millisecond 轮询,它不适合——那已超出“轮询”范畴,该考虑事件驱动或信号通知了。











