C# 異步編程與列表任務(wù)取消詳解
在實(shí)際開發(fā)中,我們經(jīng)常需要處理耗時的異步操作,比如網(wǎng)絡(luò)請求、文件讀寫等。有時候,我們可能需要取消這些正在進(jìn)行的異步操作。本文將詳細(xì)介紹如何在C#中實(shí)現(xiàn)異步操作的取消機(jī)制。
前置條件
- .NET 5.0或更高版本
- Visual Studio或Visual Studio Code
- 基本的C#異步編程知識
核心概念
在開始之前,讓我們了解幾個重要的概念:
- CancellationTokenSource用于發(fā)出取消信號的源
- CancellationToken用于接收取消信號的令牌
- Task表示異步操作的對象
完整示例代碼
下面是一個完整的示例,展示如何實(shí)現(xiàn)可取消的異步操作:
using System.Diagnostics;
namespace AppCancellationToken
{
internal class Program
{
// 創(chuàng)建取消令牌源
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
// 創(chuàng)建HttpClient實(shí)例
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
// 待下載的URL列表
static readonly IEnumerable<string> s_urlList = newstring[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/visualstudio"
};
static async Task Main()
{
Console.WriteLine("程序啟動...");
Console.WriteLine("按回車鍵取消下載...\n");
// 創(chuàng)建監(jiān)聽取消的任務(wù)
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("按回車鍵取消下載...");
}
Console.WriteLine("\n檢測到回車鍵:正在取消下載...\n");
s_cts.Cancel();
});
// 創(chuàng)建下載任務(wù)
Task sumPageSizesTask = SumPageSizesAsync();
// 等待任意一個任務(wù)完成
Task finishedTask = await Task.WhenAny(cancelTask, sumPageSizesTask);
if (finishedTask == cancelTask)
{
try
{
await sumPageSizesTask;
Console.WriteLine("在處理取消請求之前下載任務(wù)已完成。");
}
catch (OperationCanceledException)
{
Console.WriteLine("下載任務(wù)已被取消。");
}
}
Console.WriteLine("程序結(jié)束。");
}
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
Console.WriteLine($"\n總計下載字節(jié)數(shù): {total:#,#}");
Console.WriteLine($"耗時: {stopwatch.Elapsed}\n");
}
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
}
}
代碼詳解
初始化設(shè)置
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
static readonly HttpClient s_client = new HttpClient { MaxResponseContentBufferSize = 1_000_000 };
- 創(chuàng)建CancellationTokenSource實(shí)例用于發(fā)出取消信號
- 創(chuàng)建HttpClient實(shí)例用于發(fā)送HTTP請求
- 使用static readonly確保這些實(shí)例在整個應(yīng)用程序生命周期內(nèi)只創(chuàng)建一次
主方法實(shí)現(xiàn)
主方法使用async Task Main()實(shí)現(xiàn)異步入口點(diǎn),包含兩個主要任務(wù):
- 取消監(jiān)聽任務(wù)(cancelTask)
- 下載處理任務(wù)(sumPageSizesTask)
異步下載實(shí)現(xiàn)
ProcessUrlAsync方法實(shí)現(xiàn)了單個URL的下載邏輯:
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
- 使用GetAsync方法發(fā)送HTTP請求
- 傳入CancellationToken支持取消操作
- 返回下載內(nèi)容的字節(jié)數(shù)
運(yùn)行效果
程序運(yùn)行后會顯示如下輸出:
圖片
注意
- 始終使用using語句或字段初始化方式創(chuàng)建CancellationTokenSource
- 在所有可取消的異步操作中傳遞CancellationToken
- 正確處理取消異常
- 使用static readonly創(chuàng)建長期使用的HTTP客戶端實(shí)例
總結(jié)
這種模式適用于需要支持用戶取消的長時間運(yùn)行的異步操作,如網(wǎng)絡(luò)請求、文件下載等場景。