CodeGym /课程 /C# SELF /取消异步操作

取消异步操作

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

1. 介绍

当你启动一个异步(或耗时)操作时,用户(或别的代码)可能突然决定:“停!不需要了!停下来!”。比如用户决定中断一个超大文件的下载、关闭了程序窗口,或者改变主意不再从大数据库里查数据。没有取消支持的话,你的程序可能会继续执行并白白消耗资源——这对用户和机器都不够友好。

常见的取消场景:

  • 取消文件下载或取消数据提交。
  • 按用户要求快速退出复杂的数据处理流程。
  • 在长期任务突然不再需要时暂停/停止该任务。

取消是你让应用更友好、更响应、更节省资源的秘密武器。

如何在 .NET 中取消异步操作?

在 .NET 里取消耗时任务的概念是“取消令牌”(CancellationToken)。这是一个特殊对象,会传到任务的各个部分。如果有人请求取消——令牌会立刻把这个消息通知程序中所有关心它的部分。实际效果就像一面红旗:谁先看到谁停止。

在 .NET 中这个机制通过两个主要类来实现:

重要:取消令牌本身不会强制中断代码执行,它只是发出“信号”——具体怎么响应这个信号由你的应用决定。

2. 创建取消令牌并取消任务

我们用一个简单例子来看它如何工作(扩展我们的小型控制台教学程序)。

示例:支持取消的简单异步操作


using System;
using System.Threading;
using System.Threading.Tasks;

namespace DemoApp
{
    class Program
    {
        static async Task Main()
        {
            // 创建取消令牌源
            CancellationTokenSource cts = new CancellationTokenSource();

            // 启动异步任务
            Task longRunningTask = DoWorkAsync(cts.Token);

            Console.WriteLine("按任意键以取消操作...");
            Console.ReadKey();

            // 请求取消
            cts.Cancel();

            try
            {
                await longRunningTask;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("操作已被取消!");
            }
        }

        // 支持取消的异步方法
        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                // 检查取消信号
                cancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"执行步骤 {i + 1}/10...");
                await Task.Delay(1000); // 延迟 1 秒
            }

            Console.WriteLine("操作成功完成!");
        }
    }
}

这是怎么工作的?

- 我们创建了 CancellationTokenSource(变量名cts),从它那里拿到令牌(cts.Token)。
- 把令牌传给异步操作。
- 在 DoWorkAsync() 内用 ThrowIfCancellationRequested() 定期检查令牌。如果用户请求取消——该方法会抛出 OperationCanceledException,任务就会中止。
- 在 Main() 等待键盘输入后调用 cts.Cancel() 来“发信号”要求停止操作。

如果你不检查 cancellationToken.IsCancellationRequested 或不调用 ThrowIfCancellationRequested(),任务会照常继续运行——令牌只是个信息化的旗子。

3. CancellationToken:内部是怎样的?以及一点小技巧

取消令牌是一个可以方便地在方法和任务之间传递的对象,这带来了很大的灵活性:

  • 同一个令牌可以在多个异步或同步操作中复用。
  • 如果多个操作使用同一个 CancellationTokenSource 的令牌,就可以实现“群组取消”,一次取消影响所有任务。
  • 取消令牌是非侵入式的:即便你忽略它,代码也能如常执行。

怎么检查令牌:在哪儿、如何检查?

在逻辑上合适的地方检查是否有“取消标志”是必须的:循环内、每个耗时步骤、阶段切换处等。


// 在任何需要检查的点
if (cancellationToken.IsCancellationRequested)
{
    Console.WriteLine("操作已取消!退出...");
    return;
}

// 或者这样(简短并抛出异常)
cancellationToken.ThrowIfCancellationRequested();

通常使用 ThrowIfCancellationRequested() ——它会抛出一个专门的异常,调用方可以捕获它来处理取消逻辑。

4. 标准库的异步方法

很多 .NET 类和方法(尤其是异步方法)开箱即支持 CancellationToken。应该利用这些 API 来“正确”地停止操作。

下面是一个异步读取文件的例子:


using System.IO;
using System.Threading;
using System.Threading.Tasks;

class FileDemo
{
    public static async Task ReadFileWithCancelAsync(string filePath, CancellationToken cancellationToken)
    {
        using FileStream stream = File.OpenRead(filePath);
        byte[] buffer = new byte[4096];

        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
        {
            // 处理数据...
            // 如果取消令牌被请求,ReadAsync 本身会抛出 OperationCanceledException
        }
    }
}

想了解更多关于 FileStream.ReadAsyncCancellationToken 的内容,可以查看官方文档。

5. 有用的细节

超时也是一种取消!

你可以设置在超时后自动取消操作。为此可以“编程”一个带超时的取消令牌:


// 创建带超时的 CancellationTokenSource(例如 5 秒)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

经过 5 秒后令牌会自动“升起”,在下一次检查时所有使用该令牌的操作都会停止。这在你不想无限等待时非常方便。

取消发生时会怎样?

当你在令牌源上调用 Cancel(),所有使用该令牌并检查其状态的方法都会收到通知。但如果你的代码不检查令牌——取消就不会发生。

常见错误:忘了把令牌传递给所有异步和耗时操作。那样一部分操作会被取消,而另一部分会继续运行,造成不一致。

可视化:取消是怎么流转的

sequenceDiagram
    participant Main as 主线程
    participant CTS as CancellationTokenSource
    participant Task as 异步任务

    Main->>CTS: 创建 CTS,获取令牌
    Main->>Task: 把令牌传给异步任务
    Note over Task: 任务定期检查令牌
    Main->>CTS: 调用 Cancel()
    CTS-->>Task: 令牌状态变为“已取消”
    Task-->>Main: 抛出 OperationCanceledException
    Main->>Main: 捕获异常并结束工作

取消异步操作通常用在哪儿?

  • 异步下载和对服务器的请求:网络差或用户不想等时可以取消。
  • 大型计算:可以按超时或用户要求停止。
  • 网络操作、文件处理、后台大集合处理等场景。

到这里对取消异步操作的介绍就结束了——现在你的应用不仅会更快,还会更懂得“关心”用户!

6. 建议和常见错误

别忘了把取消令牌传给所有支持 cancellation 的方法和调用。如果哪儿没传——操作可能会“挂住”而无法停止。

要定期检查令牌——特别是在长循环、文件处理或大数据传输时。使用 IsCancellationRequestedThrowIfCancellationRequested()

不要尝试在外部“强行”终止线程或任务:取消令牌是礼貌的“请停止”,不是用来强制结束线程的武器。

标准库函数比如 ReadAsyncDelayHttpClient.SendAsync 等都已经支持通过令牌取消。务必利用这些功能!

处理取消时应专门捕获 OperationCanceledException ——这是表示按请求正确取消的专用异常。

1
调查/小测验
文件的异步操作第 42 级,课程 4
不可用
文件的异步操作
文件异步操作的优势
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION