异步操作中途取消的方法之一

.NET 提供了一个类方便用来发出操作取消的信号,这个类就是CancellationToken,它的好处在于它可以在任意数量的线程之间、线程池任务之间、Task之间传递信号,并且所需的代码很简单。通常用于下载超时中断、用户取消任务等情况。

CancellationToken 通常搭配 CancellationTokenSource 使用,后者是前者的一个管理类,使用 CancellationTokenSource 的 Token 属性,可以获取CancellationToken,并控制信号的发送。这两个类都属于命名空间 System.Threading

在异步编程中,只需将 Token 作为一个参数传入异步方法中。在异步方法外便能通过 CancellationTokenSource.Cancel 方法发出取消信号或者 CancelAfter 方法在一段时间后发出取消信号,这会改变 Token 的 isCancellationRequested 属性。在异步方法内,通过这个属性获取取消信号,并作出对应的处理操作。

例如下面的代码:

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

namespace CancellationTokenTest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            //cts.Cancel() //立即发出取消信号
            //3秒后发出取消信号,模拟取消行为
            cts.CancelAfter(3000);
            Console.WriteLine("下载开始");
            await DownloadAsync(cts.Token);
            Console.ReadKey();
        }

        static async Task DownloadAsync(CancellationToken ct)
        {
            using (HttpClient client = new HttpClient())
            {
                //模拟一个比较耗时的下载的过程
                for (int i = 0; i < 30; i++)
                {
                    string s = await client.GetStringAsync("https://kfm.ink");
                    Console.WriteLine(s);

                    //ct.ThrowIfCancellationRequested();//直接抛出异常
                    //判断是否需要取消,并自行处理
                    if (ct.IsCancellationRequested)
                    {
                        Console.WriteLine("下载取消");
                        break;
                    }
                }
                
            }
        }

    }
}

这里除了通过 IsCancellationRequested 属性判断是否需要取消外,还可以通过 ThrowIfCancellationRequested 方法在需要取消时立即抛出异常,该异常是 OperationCanceledException

.NET 很多库的异步方法都可以传入 Token,使用时传入该参数可以降低代码的粒度,例如上面这个例子,至少执行一次 GetStringAsync 才有可能中断,而这一次执行可能耗费大量的时间,通过使用 GetAsync 方法可以解决这个问题:

//传入Token
HttpResponseMessage response = await client.GetAsync("https://kfm.ink/", ct);
string s = await response.Content.ReadAsStringAsync();
Console.WriteLine(s);