Поделиться через


Отмена списка задач

Вы можете отменить асинхронное консольное приложение, если вы не хотите ждать завершения. Следуя примеру в этом разделе, вы можете добавить отмену в приложение, которое скачивает содержимое списка веб-сайтов. Можно отменить многие задачи, связав экземпляр CancellationTokenSource с каждой задачей. Если выбрать клавишу , вы отмените все задачи, которые еще не завершены.

Темы, рассматриваемые в этом руководстве:

  • Создание консольного приложения .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 теперь считается асинхронным главным , что позволяет создать асинхронную точку входа в исполняемый файл. Он выводит несколько инструкций в консоль, а затем объявляет экземпляр Task с именем cancelTask, считывающий нажатия клавиш консоли. Если нажатие клавиши ввод ввод вызывается CancellationTokenSource.Cancel(). Это сигнализирует об отмене. Затем переменная sumPageSizesTask назначается из метода SumPageSizesAsync. Затем обе задачи передаются в 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. Затем он циклит по каждому URL-адресу в 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;
}

Добавление метода процесса

Добавьте следующий метод ProcessUrlAsync ниже метода SumPageSizesAsync:

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

См. также

Дальнейшие действия