次の方法で共有


タスクの一覧を取り消す

非同期コンソール アプリケーションが完了するのを待たなければ、非同期コンソール アプリケーションを取り消すことができます。 このトピックの例に従うことで、Web サイトの一覧の内容をダウンロードするアプリケーションにキャンセルを追加できます。 CancellationTokenSource インスタンスを各タスクに関連付けることで、多くのタスクを取り消すことができます。 Enter キーを選択した場合は、まだ完了していないすべてのタスクを取り消します。

このチュートリアルの内容:

  • .NET コンソール アプリケーションの作成
  • キャンセルをサポートする非同期アプリケーションの記述
  • キャンセル通知のデモンストレーション

前提条件

  • 最新の .NET SDK
  • Visual Studio Codeエディター
  • C# DevKit

サンプル アプリケーションを作成する

新しい .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 メソッドは 非同期メインと見なされるようになりました。これにより、実行可能ファイルへの非同期エントリ ポイントが可能になります。 コンソールにいくつかの指示メッセージを書き込み、cancelTaskという名前のTask インスタンスを宣言します。これにより、コンソール のキー ストロークが読み取られます。 Enter キーを押すと、CancellationTokenSource.Cancel()の呼び出しが行われます。 これにより、キャンセルが通知されます。 次に、SumPageSizesAsync メソッドからsumPageSizesTask変数が割り当てられます。 その後、両方のタスクが 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;
    }
}

こちらもご覧ください

次のステップ