CodeGym /课程 /C# SELF /操作取消:CancellationToken

操作取消:CancellationToken

C# SELF
第 60 级 , 课程 4
可用

1. 认识 CancellationToken

假设你遇到这样一种情况:你在进行一个长时间的文件下载,然后突然想到,你的网络套餐有限,流量宝贵。或者你的应用用户搞不清楚,启动了某个计算,后来觉得没必要了——他希望点击“取消”按钮,不用等待程序优雅地完成。现代应用要保持响应性,就必须能随时接收停止信号,正确中断异步操作。

为此,.NET 提供了一整套取消基础设施——CancellationToken

CancellationToken(字面意思“取消令牌”)是一个特殊对象,你可以在长时间运行的操作中传递它。任何时候,你都可以“设置标志”——发出取消信号,操作需要周期性检查这个标志,并在检测到取消请求时尽可能优雅地终止。

它是怎么工作的?(简要)

  • 有一个 CancellationTokenSource 对象,它“生成”token并能取消它们。
  • CancellationToken 本身是“信号旗”,可以分发给多个操作。
  • 操作会定期检查token:如果检测到取消请求,就退出。

比喻一下:你是老板(你就是 CancellationTokenSource)。你给员工发“工作牌”——CancellationToken。当你觉得该全部取消时,就升起红旗——所有持有“工作牌”的员工马上撤退,留下一桌未吃完的饭菜。

2. 如何使用 CancellationToken

创建取消源 (CancellationTokenSource)

var cts = new CancellationTokenSource();

获取token (CancellationToken)

CancellationToken token = cts.Token;

将token传入异步方法

大部分.NET的标准异步方法都支持传入 CancellationToken 参数。例如,HttpClient.GetAsyncStream.ReadAsyncTask.Delay 等。

示例——带取消的延时:

await Task.Delay(10000, token); // 等待10秒,但可以取消!

请求取消(比如按钮点击或定时器触发)

cts.Cancel(); // 所有收到这个token的操作都会知道取消了

在方法内部检查token

在自己的方法中(尤其是耗时长、循环多的场景),需要定期检查取消标志,并在检测到取消时抛出 OperationCanceledException

token.ThrowIfCancellationRequested();

或者直接检查属性:

if (token.IsCancellationRequested)
{
    // 释放资源,退出方法
}

3. 示例:给我们的学习应用加上取消功能

假设我们有个应用,从网站下载数据。现在要加个功能,让用户可以随时取消下载。

基础异步下载示例

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class Downloader
{
    public async Task DownloadAsync(string url)
    {
        var client = new HttpClient();
        string content = await client.GetStringAsync(url); // 不支持取消
        Console.WriteLine("下载完成!");
    }
}

加入 CancellationToken

public async Task DownloadAsync(string url, CancellationToken token)
{
    var client = new HttpClient();
    string content = await client.GetStringAsync(url, token); // 现在支持取消了!
    Console.WriteLine("下载完成!");
}

由调用端控制取消

static async Task Main(string[] args)
{
    var downloader = new Downloader();
    var cts = new CancellationTokenSource();

    Console.WriteLine("请输入要下载的URL:");
    string url = Console.ReadLine();

    var downloadTask = downloader.DownloadAsync(url, cts.Token);

    Console.WriteLine("按任意键取消下载...");
    Console.ReadKey();

    cts.Cancel(); // 发送取消信号

    try
    {
        await downloadTask;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("用户取消了下载!");
    }
}

就这么简单!用户可以在任何时候中断操作。

4. CancellationTokenSource 和方法的交互

flowchart TD
    A["用户代码(Main)"] -- 创建 --> B["CancellationTokenSource"]
    B -- 生成 --> C["CancellationToken"]
    C -- 传递 --> D["异步操作"]
    A -- 调用 Cancel() --> B
    D -- 定期检查 --> C
    C -- 通知取消 --> D
    D -- 抛出异常或结束 --> A

5. 取消的处理

当操作收到取消token时,有两种行为:

.NET 方法会自动抛出异常
如果你调用的标准方法(如 Stream.ReadAsyncHttpClient.GetAsyncTask.Delay)支持传入token,一旦调用 Cancel(),它们会自动抛出 OperationCanceledException。你只需捕获这个异常即可。

自定义异步代码
如果你自己实现“耗时”操作,比如循环处理或复杂计算,责任在你——要定期检查 token.IsCancellationRequested(或调用 token.ThrowIfCancellationRequested()),以便正确响应取消请求。

示例:带手动取消检测的“长时间”操作

public async Task CalculatePrimesAsync(int max, CancellationToken token)
{
    for (int i = 2; i < max; i++)
    {
        token.ThrowIfCancellationRequested(); // 检查取消

        if (IsPrime(i))
        {
            Console.WriteLine($"素数:{i}");
            await Task.Delay(100, token); // 给点“休息”时间(支持取消)
        }
    }
    Console.WriteLine("计算完成!");
}

private bool IsPrime(int n)
{
    for (int i = 2; i <= Math.Sqrt(n); i++)
        if (n % i == 0) return false;
    return true;
}

6. 一些实用细节

支持 CancellationToken 的方法和类

类/方法 支持 CancellationToken? 示例
Task.Delay await Task.Delay(1000, token);
HttpClient.GetAsync await client.GetAsync(url, token);
Stream.ReadAsync await stream.ReadAsync(buffer, 0, len, token);
Task.Run Task.Run(() => ..., token);
File.ReadAllTextAsync await File.ReadAllTextAsync(path, token);
Thread.Sleep (不支持;建议用 Task.Delay)
你自己写的方法 ✔(如果支持的话!) MyLongMethod(token)

可取消操作的生命周期示意

sequenceDiagram
    participant 用户
    participant Main
    participant CancellationTokenSource
    participant 异步操作

    用户->>Main: 启动操作
    Main->>CancellationTokenSource: 创建 CTS
    Main->>异步操作: 启动并传递 CancellationToken
    用户->>Main: 点击“取消”
    Main->>CancellationTokenSource: 调用 Cancel()
    异步操作->>异步操作: 发现取消(\nIsCancellationRequested)
    异步操作-->>Main: 抛出 OperationCanceledException
    Main->>用户: 显示“操作已取消”消息

实际应用场景

  • UI应用: 用户想中断长时间加载、计算、文件操作(比如关闭窗口或取消按钮)
  • 服务器端: 客户端断开连接时,立即取消请求处理,节省资源
  • 大数据处理: 任务可能耗时很长,要提供停止计算或迁移的途径
  • 硬件交互: 扫描、打印等操作需要支持紧急取消

7. 常见错误和注意事项

有些开发者会高估 CancellationToken 的能力,觉得取消是瞬间完成的。实际上,取消只是操作的“自愿”行为。如果操作没有定期检查token,任何“宇宙力量”都无法强制它停止(除非直接终止整个进程,但那是极端情况)。

另外,取消并不保证“状态”会被保存。如果你在写文件或数据库,必须自己处理事务回滚或资源释放。Token 只是个信号,具体怎么反应由代码决定。

还有个细节:如果把“已取消”的token传给方法,它会立即抛出异常。工作中要注意避免重复使用已取消的token,除非你明确知道要这样做。

2
任务
C# SELF, 第 60 级, 课程 4
已锁定
支持取消操作的素数查找
支持取消操作的素数查找
1
调查/小测验
Task和Thread的比较第 60 级,课程 4
不可用
Task和Thread的比较
数据的并行处理
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION