共用方式為


取消工作清單

如果您不想等待異步主控台應用程式完成,您可以取消它。 依照本主題中的範例,您可以在下載網站清單內容的應用程式中加入取消功能。 您可以將 CancellationTokenSource 實例與每個工作建立關聯,以取消許多工作。 如果您選取 [Enter 鍵,則會取消尚未完成的所有工作。

此教學課程涵蓋:

  • 建立 .NET 主控台應用程式
  • 撰寫擁有支援取消功能的非同步應用程式
  • 示範訊號取消

先決條件

建立範例應用程式

建立新的 .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 方法現在被視為 非同步主方法,這允許異步進入可執行檔。 它會將一些指示訊息寫入控制台,然後宣告一個名為 cancelTaskTask 實例,該實例將讀取控制台按鍵輸入。 如果按下 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;
    }
}

另請參閱

後續步驟