【筆記】C# CancellationToken

bruceWANG18發表於2024-05-04

.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);

相關文章