Task无原生暂停机制,必须通过CancellationToken实现协作式取消:在循环或等待点调用ThrowIfCancellationRequested()或传入token的异步方法,并正确传递和处置CancellationTokenSource。
Task 没有原生暂停机制,别调用 Suspend 或找“暂停方法”
task 本身不支持暂停(pause/resume),这是设计使然——它不是线程,也不是协程,没有挂起上下文的能力。试图用 thread.suspend(已废弃)、manualresetevent 手动阻塞内部逻辑,或幻想存在 task.pause(),都会导致不可控的死锁、资源卡住或取消失效。
真正可行的路径只有一条:让任务自己「响应中断信号」,在合适的位置主动检查并退出。这依赖 CancellationToken 配合协作式取消(cooperative cancellation)。
- 所有长期运行、循环处理、I/O 等待的 Task,必须在循环体/等待点插入
token.ThrowIfCancellationRequested()或await xxxAsync(token) - 不要在 catch 块里吞掉
OperationCanceledException,否则取消就静默失败了 - 如果任务已开始执行但尚未响应 token,它不会被强制终止;取消只是“通知”,不是“杀进程”
正确创建和传递 CancellationToken 的三个关键点
Token 不是全局单例,也不是随便 new 出来的。它的生命周期、来源和传播方式直接决定能否取消成功。
- 用
CancellationTokenSource创建源头:var cts = new CancellationTokenSource();,后续通过cts.Token分发 - 启动 Task 时必须显式传入 token(作为参数),不能靠闭包捕获或静态字段共享——否则异步栈可能丢失引用
- 若需超时控制,优先用
cts.CancelAfter(3000),而不是自己开 Timer 调用Cancel()
示例:
var cts = new CancellationTokenSource();
var task = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
cts.Token.ThrowIfCancellationRequested(); // 关键检查点
Thread.Sleep(100);
}
}, cts.Token); // 必须传入
await 场景下取消容易漏掉的两个坑
async/await 是取消最常出问题的地方:表面看写了 await DoWorkAsync(token),但实际没生效,原因往往藏在底层实现里。
- 确保被 await 的方法真正接受并使用了
CancellationToken参数——比如HttpClient.GetAsync(uri, token)有效,但Task.Delay(1000)不带 token 就无法响应外部取消 - 不要把 token 传给一个不支持取消的异步方法(例如某些老 SDK 的封装),它会忽略 token,导致上层
cts.Cancel()后仍在跑 - 在
try/catch中 await 时,OperationCanceledException可能被包装成AggregateException,需用ex.InnerException is OperationCanceledException判断
取消后状态和资源清理经常被忽略的细节
调用 cts.Cancel() 后,Task 状态变成 IsCanceled == true,但这不等于资源已释放。很多场景下,文件流、数据库连接、定时器还在运行。
- 务必在
finally或using块中释放非托管资源,不要依赖 Task 结束自动清理 -
CancellationToken.Register()可用于注册清理回调,但要注意:回调执行时机不确定,且不能抛异常,适合轻量级通知 - 如果 Task 已完成(无论成功、失败或取消),再次调用
cts.Cancel()无副作用,但反复创建新CancellationTokenSource而不Dispose()会泄漏句柄
复杂点在于:取消可能发生在任意代码行之间,而 .NET 不提供原子性的“取消+回滚”语义。你得自己设计可中断的步骤边界,并在每个边界点判断 token 状态。


















