CancelAfter 本质是基于 Timer 实现的延迟取消
CancelAfter并不是启动一个高精度计时器,而是内部调用
System.Threading.Timer,以指定毫秒数为间隔触发一次
Cancel()。这意味着它的触发时机受 .NET 线程池调度、系统时钟粒度和 GC 暂停影响,**无法保证精确到毫秒级**。
典型场景是“最多等待 3 秒,超时就放弃”,而不是“必须在第 3000 毫秒整点取消”。如果你看到实际取消发生在 3012ms 或 3040ms,这是正常行为。
Windows 默认系统时钟分辨率约 15.6ms(可通过timeBeginPeriod(1)提升,但不推荐在普通应用中使用) .NET 6+ 在部分平台(如 Linux with epoll)对
Timer做了优化,但
CancelAfter仍不提供 sub-millisecond 保证 频繁创建短时
CancelAfter(1)不仅无效,还会增加线程池压力和 GC 负担
CancelAfter 和手动 Timer 的行为差异
直接用
System.Threading.Timer可以控制回调线程上下文、复用实例、甚至尝试补偿误差;而
CancelAfter是一次性、黑盒封装,调用后无法获取底层
Timer实例,也无法重置或查询剩余时间。
例如,你不能像这样“续期”:
cts.CancelAfter(2000); // ❌ 下面这行不会延长原定时器,而是新建一个,旧的仍会在 ~2s 后触发 cts.CancelAfter(5000);
如果需要动态调整超时,应改用
Timer手动管理,或每次取消旧的再新建新的
CancellationTokenSource。
CancelAfter调用后,若
cts.Token.IsCancellationRequested已为
true,新定时器会被静默忽略 重复调用
CancelAfter会丢弃前一个定时器(内部调用
Change(Timeout.Infinite, Timeout.Infinite)),但存在极小窗口期可能触发两次取消(罕见,仅当前次回调已入队但尚未执行) 它不参与
SynchronizationContext,回调总在线程池线程上执行
常见误用:把 CancelAfter 当作 Sleep 或轮询替代品
有人试图用
cts.CancelAfter(100); Thread.Sleep(100);来“等待 100ms 或提前取消”,这是危险的——
CancelAfter不阻塞,
Thread.Sleep也不响应 token,两者完全解耦。
正确做法是配合可取消的等待操作,例如:
var cts = new CancellationTokenSource(); cts.CancelAfter(100); await Task.Delay(1000, cts.Token); // 真正响应取消
或者用于 I/O 操作:
using var httpClient = new HttpClient();
var cts = new CancellationTokenSource();
cts.CancelAfter(5000);
await httpClient.GetAsync("https://api.example.com", cts.Token);
不要在同步上下文中依赖 CancelAfter实现“定时检查”逻辑;它不提供回调通知,也不返回句柄 不要用它替代
Task.Run(() => { Thread.Sleep(100); }).Wait(cts.Token) —— 这种写法既低效又无法真正中断 Thread.Sleep在 ASP.NET Core 中,
HttpContext.RequestAborted已自带请求级取消,一般无需额外套一层
CancelAfter
精度不够时的实际应对策略
如果你的业务确实对延迟敏感(比如实时音视频帧同步、高频交易准备阶段),
CancelAfter就不该出现在关键路径上。此时应切换方案: 用
Stopwatch+ 循环轮询
token.IsCancellationRequested(适合短时、CPU 可接受占用) 使用
IOCP-base 的异步原语(如
Socket.AwaitableSocketAsyncEventArgs配合自定义超时) 在 Windows 上考虑
WaitHandle.WaitAny组合
cts.Token.WaitHandle和
new AutoResetEvent()+
Set()定时触发(需另启线程或
Timer) 第三方库如
System.Threading.Channels的
Reader.WaitToReadAsync(cts.Token)内部做了更精细的超时处理,可间接利用
多数 Web API 调用、数据库查询、文件读写场景中,±20ms 的偏差完全可接受,
CancelAfter仍是简洁安全的选择——前提是别把它当成精密仪器来用。
