取消工作清單

如果您不想要等候非同步主控台應用程式完成,您可以取消該應用程式。 遵循本主題中的範例,即可將取消新增至下載網站清單內容的應用程式。 您可以將 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/xamarin"
};

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,其允許可執行檔中的非同步進入點。 這會將一些指示訊息寫入主控台,然後宣告名為 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 中的每個 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/xamarin"
    };

    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;
    }
}

另請參閱

下一步