次の方法で共有


タスクの一覧を取り消す

非同期のコンソール アプリケーションは、終了まで待機しない場合にキャンセルすることができます。 このトピックの例に従うと、Web サイトの一覧のコンテンツをダウンロードするアプリケーションにキャンセルを追加できます。 各タスクに 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 クラス定義で、次の 3 つのフィールドを追加します。

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() の呼び出しが行われます。 これにより、キャンセルが通知されるようになります。 次に、sumPageSizesTask 変数が SumPageSizesAsync メソッドから割り当てられています。 両方のタスクは次に Task.WhenAny(Task[]) に渡され、2 つのタスクのいずれかが完了したときに続行されるようになります。

コードの次のブロックは、キャンセルが処理されるまで、アプリケーションが終了しないようにします。 最初に完了するタスクが 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 を呼び出します。 反復処理のたびに、s_cts.TokenProcessUrlAsync メソッドに渡され、コードから 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;
    }
}

関連項目

次の手順