CancellationToken仅发信号,不自动取消操作;任务是否停止取决于代码是否在关键位置检查并响应它。常见问题包括未传入或未使用token、调用不支持取消的同步API、异步链中断、循环中缺少轮询等;应手动插入ThrowIfCancellationRequested、用Task.Delay替代Thread.Sleep、合理注册回调处理阻塞调用,并注意CTS生命周期与资源清理。

CancellationToken 本身不取消任何东西,它只发信号;任务是否停止,完全取决于你有没有在关键位置检查它。
为什么调用 Cancel() 后任务还在跑
常见现象:调用 cts.Cancel() 后,await DoWorkAsync(token) 没抛 OperationCanceledException,日志照打、循环照转。根本原因不是 token 失效,而是目标代码压根没响应。
- 方法签名里没带
CancellationToken参数,或传了但没在内部使用 - 用了不支持取消的 API,比如
Thread.Sleep(5000)、File.ReadAllText(path)—— 这些是同步阻塞,无法被 token 中断 - 异步链中断:比如
DoWorkAsync()调用了HttpClient.GetStringAsync(url),却漏传token - 只在方法开头检查一次
token.IsCancellationRequested,后续长循环或大块计算中再没轮询
Task.Delay 能取消,但你自己写的循环不能?
因为 Task.Delay(int, CancellationToken) 是 .NET 原生支持取消的异步方法,内部做了轮询和 socket 层中断;而 while (true) { /* ... */ } 是纯 CPU 密集型逻辑,没有 await、没有 I/O 等待点,token 对它完全透明。
- 必须手动插入检查点:在循环体开头加
token.ThrowIfCancellationRequested() - 避免用
Thread.Sleep,改用await Task.Delay(ms, token) - 大数组处理时,每处理 N 项后检查一次 token,而不是等全部做完才看
- 不要写
Task.Run(() => { while(...) { Thread.Sleep(...); } })—— 这只是把同步代码扔进线程池,cancel 只能中断Wait(),不能中断Thread.Sleep
如何让不支持取消的同步 API 响应 token
第三方 SDK、串口通信、Process.WaitForExit() 等纯阻塞调用,无法直接接受 CancellationToken。这时得靠 token.Register() 注册回调,在取消触发时执行“中断动作”。
- 注册必须在阻塞调用开始前完成,否则存在竞态:先
token.Register(() => process.Kill()),再process.WaitForExit() - 回调里只能做同步、无 await、不抛异常的操作(如
Close()、Kill()、设置volatile bool _shouldStop = true) - 回调运行在线程池线程上,注意线程安全;如果要更新 UI,需调度回主线程
- 别忘了保存
token.Register(...)返回的IDisposable,并在操作结束时Dispose()解绑,否则可能内存泄漏
CancellationTokenSource 生命周期管理的坑
一个 CancellationTokenSource 实例只能调用一次 Cancel(),之后再调用会抛 ObjectDisposedException;且它的 Token 一旦被取消,就永远处于“已取消”状态,不可复用。
- 每个独立可取消操作,都该用新的
CancellationTokenSource,别复用旧实例 - 不要在
using块里创建它,却把Token传给长期存活的对象(如事件处理器、单例服务),Dispose()后 token 失效但调用方无感知 - 超时场景优先用
new CancellationTokenSource(TimeSpan.FromSeconds(5)),比手写定时器 +Cancel()更可靠 - 组合多个取消源时,用
CancellationTokenSource.CreateLinkedTokenSource(token1, token2),别自己轮询多个IsCancellationRequested
最常被忽略的一点:取消不是终点,而是清理的起点。OperationCanceledException 是正常流程,不是错误;catch 它之后,必须确保文件流、数据库连接、子进程等资源被释放——否则 token 再准也没用。


















