c# CancellationToken 的用法 c#如何取消一个异步任务

来源:这里教程网 时间:2026-02-21 17:36:00 作者:

直接说结论:

CancellationToken
本身不能“取消”任务,它只是个信号令牌;真正实现取消的是你写的代码是否响应这个信号——不检查、不抛异常、不退出,
Cancel()
就是按了个静音键。

怎么创建和传递 CancellationToken?

核心就三步:建

CancellationTokenSource
→ 拿
Token
→ 往异步方法里传。绝大多数内置异步 API(比如
HttpClient.GetAsync
StreamReader.ReadLineAsync
)都支持接收
CancellationToken
参数,这是约定俗成的最后一个可选参数。

CancellationTokenSource
是“发号施令的人”,调用
Cancel()
CancelAfter(3000)
就是下命令
CancellationToken
是“传令兵”,只读、不可变,任务靠它轮询或注册回调
别自己 new
CancellationToken
—— 必须从
CancellationTokenSource.Token
获取

为什么 await HttpClient.GetAsync(token) 会真的被取消?

因为

HttpClient
内部做了响应:它在底层 socket 操作中监听了
token.IsCancellationRequested
,一旦为
true
就立即中断请求并抛出
OperationCanceledException
。这不是魔法,是微软在 SDK 里写死了的协作逻辑。

你用的大多数 .NET 基础类库(
FileStream
Task.Delay
Timer
等)都原生支持
CancellationToken
但你自己写的长循环、CPU 密集型计算、或调用非托管代码时,必须手动检查,否则取消完全无效 错误写法:
await Task.Delay(1000);
—— 没传
token
,就无法被外部中断
正确写法:
await Task.Delay(1000, token);
或在循环中加
token.ThrowIfCancellationRequested();

常见踩坑:取消后任务还在跑、没进 catch、资源没释放

最典型的问题不是“不会用”,而是“用了但没全覆盖”。比如在

try
里开了文件流、连了数据库,却只在
await
处检查 token,忘了在 finally 或
using
外做清理。

OperationCanceledException
是正常流程,不是 bug,应该显式
catch
并区分处理(比如不记日志、不弹错误框)
别在
catch (Exception)
里吞掉
OperationCanceledException
,否则你永远不知道任务是不是被取消了
要用
token.Register(() => { /* 清理资源 */ })
注册回调,尤其当取消可能发生在非 await 路径(如同步计算中途)
超时场景优先用
new CancellationTokenSource(TimeSpan.FromSeconds(5))
,比手写定时器 +
Cancel()
更可靠
static async Task LongRunningOperationAsync(CancellationToken token)
{
    using var registration = token.Register(() => Console.WriteLine("已触发取消回调,释放资源"));
<pre class='brush:php;toolbar:false;'>for (int i = 0; i < 100; i++)
{
    token.ThrowIfCancellationRequested(); // 关键:主动抛异常,让调用栈快速退出
    await Task.Delay(100, token);          // 关键:所有 await 都带 token
    Console.WriteLine($"进度: {i + 1}%");
}

}

真正难的从来不是怎么写

cts.Cancel()
,而是想清楚:你的业务逻辑里,哪些步骤可中断、哪些必须原子完成、哪些资源必须确保释放——
CancellationToken
只提供机制,不替你做决策。

相关推荐