cancellationtoken仅发信号,不自动取消操作;任务持续运行是因为代码未检查或响应取消信号,如未传token、未调用throwifcancellationrequested、混用cts实例等。

CancellationToken 本身不会取消任何东西,它只负责发信号;任务是否真的停,取决于你有没有在关键位置检查它、响应它、清理它。
为什么调用了 Cancel() 任务还在跑
根本原因不是 token 失效,而是目标代码压根没收到或没理这个信号。常见情况包括:
-
HttpClient.GetAsync(url)没传cancellationToken参数——网络请求继续发,完全无视取消指令 - 自己写的
while (true)循环里没插token.ThrowIfCancellationRequested()或if (token.IsCancellationRequested) break; - 用了
Task.Run(() => Thread.Sleep(5000))这种纯同步阻塞,而Thread.Sleep不接受 token,也无法被中断 - 异步方法内部调用了不支持取消的同步 API(比如
File.ReadAllText(path)),却没换成File.ReadAllTextAsync(path, token)
CancelAfter() 总是不生效的三个硬伤
它不是倒计时开关,而是注册一个延迟回调;一旦 CancellationTokenSource 被释放或提前取消,这个回调就永远不执行。
- 写在
await之后:比如await DoWorkAsync(); cts.CancelAfter(3000);—— 此时任务早结束了,超时逻辑形同虚设 - 目标方法根本不收 token:比如
DoWorkAsync()签名里没有CancellationToken参数,那CancelAfter再准也没用 - 混用了 token:
DoWorkAsync(cts1.Token),却对cts2.CancelAfter(3000)—— 信号发错了地方
正确顺序只能是:var cts = new CancellationTokenSource(); cts.CancelAfter(3500); await DoWorkAsync(cts.Token);
UI 场景下“取消按钮”点不动的真相
90% 是因为 CancellationTokenSource 实例生命周期不对——它被创建在事件处理方法里,一退出就丢了引用。
- 错误写法:
private void StartButton_Click(...) { var cts = new CancellationTokenSource(); ... }——cts方法一结束就不可达 - 正确做法:声明为类字段
private CancellationTokenSource? _cts;,点击“开始”时赋值_cts = new CancellationTokenSource();,点击“取消”时调用_cts?.Cancel(); - 务必确认下游所有异步调用都透传了同一个
_cts.Token,比如client.GetAsync(url, _cts.Token),漏传一层,取消链就断了
同步阻塞操作怎么响应取消
不能靠轮询 IsCancellationRequested,必须用 Register() 绑定中断动作,且注册要在阻塞调用开始前完成。
- 典型场景:调用第三方 SDK 的阻塞读取、
Process.WaitForExit()、串口ReadLine() - 注册写法:
token.Register(() => { if (_process != null && !_process.HasExited) _process.Kill(); }); - 注意:
Register回调运行在线程池线程上,不能await、不能抛异常(会静默吞掉)、只能做清理动作(关句柄、设标志、杀进程) - 别在注册后立刻
Dispose()CancellationTokenSource,否则回调永远不会触发
最易被忽略的一点:CancellationTokenSource 必须活到整个操作结束——哪怕你只用它传一个 token,只要下游还有可能响应取消,它就不能提前释放。复用已 Cancel 过的实例、在 using 块里创建并暴露 token,都是高危操作。











