取消工作清單
如果您不想等待異步主控台應用程式完成,您可以取消它。 依照本主題中的範例,您可以在下載網站清單內容的應用程式中加入取消功能。 您可以將 CancellationTokenSource 實例與每個工作建立關聯,以取消許多工作。 如果您選取 [Enter 鍵,則會取消尚未完成的所有工作。
此教學課程涵蓋:
- 建立 .NET 主控台應用程式
- 撰寫擁有支援取消功能的非同步應用程式
- 示範訊號取消
先決條件
- 最新 .NET SDK
- Visual Studio Code 編輯器
- C# 開發套件
建立範例應用程式
建立新的 .NET Core 控制台應用程式。 您可以使用 dotnet new console
命令,或從 Visual Studio建立一個。 在慣用的程式代碼編輯器中開啟 Program.cs 檔案。
取代 using 指示詞
以下列宣告取代現有的 using
指示詞:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
新增欄位
在 Program
類別定義中,新增下列三個字段:
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
CancellationTokenSource 用來向 CancellationToken發出要求取消的訊號。
HttpClient
會公開傳送 HTTP 要求和接收 HTTP 回應的能力。
s_urlList
會保存應用程式計劃處理的所有URL。
更新應用程式進入點
主控台應用程式的主要進入點是 Main
方法。 將現有的方法取代為下列方法:
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (TaskCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
更新的 Main
方法現在被視為 非同步主方法,這允許異步進入可執行檔。 它會將一些指示訊息寫入控制台,然後宣告一個名為 cancelTask
的 Task 實例,該實例將讀取控制台按鍵輸入。 如果按下 Enter 鍵,就會呼叫 CancellationTokenSource.Cancel()。 這會發出取消訊號。 接下來,會從 SumPageSizesAsync
方法指派 sumPageSizesTask
變數。 然後,這兩項任務都會被交給 Task.WhenAny(Task[]),一旦其中任何一項完成就會繼續。
下一個程式代碼區塊可確保在取消被處理後,應用程式才會結束。 如果第一個完成的工作是 cancelTask
,則會等候 sumPageSizeTask
。 如果已取消,則在等候時會引發 System.Threading.Tasks.TaskCanceledException。 區塊會攔截該例外狀況,並列印訊息。
建立異步計算頁面大小總和的函數
在 Main
方法下方,新增 SumPageSizesAsync
方法:
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($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
方法會從實例化並啟動 Stopwatch開始。 然後它會在 s_urlList
中迴圈檢查每個網址,並呼叫 ProcessUrlAsync
。 每次反覆專案時,s_cts.Token
都會傳遞至 ProcessUrlAsync
方法,而程式代碼會傳回 Task<TResult>,其中 TResult
為整數:
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
新增程序方法
在 SumPageSizesAsync
方法下方新增下列 ProcessUrlAsync
方法:
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;
}
對於任何指定的 URL,方法會使用提供的 client
實例,以取得回應作為 byte[]
。
CancellationToken 實例會傳遞至 HttpClient.GetAsync(String, CancellationToken) 和 HttpContent.ReadAsByteArrayAsync() 方法。
token
用於登記取消請求。 URL 和長度寫入主控台之後,長度會被傳回。
應用程式輸出範例
Application started.
Press the ENTER key to cancel...
https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
https://learn.microsoft.com/dotnet 67,452
https://learn.microsoft.com/dynamics365 48,582
https://learn.microsoft.com/education 22,924
ENTER key pressed: cancelling downloads.
Application ending.
完整範例
下列程式代碼是範例中 Program.cs 檔案的完整文字。
using System.Diagnostics;
class Program
{
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
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($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {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;
}
}