取消工作清單
如果您不想要等候非同步主控台應用程式完成,您可以取消該應用程式。 遵循本主題中的範例,即可將取消新增至下載網站清單內容的應用程式。 您可以將 CancellationTokenSource 執行個體與每個工作建立關聯,來取消多項工作。 如果您選取 Enter 鍵,則會取消尚未完成的所有工作。
此教學課程涵蓋:
- 建立 .NET 主控台應用程式
- 撰寫支援取消的非同步應用程式
- 示範如何發出取消訊號
必要條件
本教學課程需要下列:
- .NET 5 或更新版本的 SDK
- 整合式開發環境 (IDE)
建立範例應用程式
建立新的 .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
方法現在會視為 Async 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
中的每個 URL 執行迴圈,並呼叫 ProcessUrlAsync
。 With each iteration, the s_cts.Token
is passed into the ProcessUrlAsync
method and the code returns a Task<TResult>, where TResult
is an integer:
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;
}
}