Partilhar via


Cancelar uma lista de tarefas

Você pode cancelar um aplicativo de console assíncrono se não quiser esperar que ele termine. Seguindo o exemplo neste tópico, você pode adicionar um cancelamento a um aplicativo que baixa o conteúdo de uma lista de sites. Você pode cancelar muitas tarefas associando a instância a CancellationTokenSource cada tarefa. Se você selecionar a tecla Enter , cancelará todas as tarefas que ainda não foram concluídas.

Este tutorial aborda:

  • Criando um aplicativo de console .NET
  • Escrevendo um aplicativo assíncrono que suporta cancelamento
  • Demonstração do cancelamento da sinalização

Pré-requisitos

Neste tutorial necessita do seguinte:

Criar aplicativo de exemplo

Crie um novo aplicativo de console .NET Core. Você pode criar um usando o dotnet new console comando ou do Visual Studio. Abra o arquivo Program.cs em seu editor de código favorito.

Substituir usando instruções

Substitua as instruções using existentes por estas declarações:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

Adicionar campos

Na definição de Program classe, adicione estes três campos:

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

O CancellationTokenSource é usado para sinalizar um cancelamento solicitado para um CancellationToken. O HttpClient expõe a capacidade de enviar solicitações HTTP e receber respostas HTTP. O s_urlList contém todas as URLs que o aplicativo planeja processar.

Atualizar o ponto de entrada do aplicativo

O principal ponto de entrada no aplicativo de console é o Main método. Substitua o método existente pelo seguinte:

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.");
}

O método atualizado Main agora é considerado um principal assíncrono, que permite um ponto de entrada assíncrono no executável. Ele grava algumas mensagens instrutivas no console e, em seguida, declara uma Task instância chamada cancelTask, que lerá os pressionamentos de teclas do console. Se a tecla Enter for pressionada, será feita uma chamada para CancellationTokenSource.Cancel() . Isso sinalizará o cancelamento. Em seguida, a variável é atribuída a sumPageSizesTask partir do SumPageSizesAsync método. Ambas as tarefas são então passadas para Task.WhenAny(Task[]), que continuará quando qualquer uma das duas tarefas for concluída.

O próximo bloco de código garante que o aplicativo não saia até que o cancelamento tenha sido processado. Se a primeira tarefa a ser concluída for o , o cancelTasksumPageSizeTask é aguardado. Se foi cancelado, quando aguardado lança um System.Threading.Tasks.TaskCanceledException. O bloco captura essa exceção e imprime uma mensagem.

Criar o método de tamanhos de página de soma assíncrona

Abaixo do Main método, adicione o SumPageSizesAsync método:

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

O método começa instanciando e iniciando um Stopwatcharquivo . Em seguida, ele faz um loop através de cada URL no s_urlList e chama ProcessUrlAsync. Com cada iteração, o é passado para o método e o s_cts.TokenProcessUrlAsync código retorna um , onde TResult é um Task<TResult>inteiro:

int total = 0;
foreach (string url in s_urlList)
{
    int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
    total += contentLength;
}

Adicionar método de processo

Adicione o seguinte ProcessUrlAsync método abaixo do SumPageSizesAsync método:

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

Para qualquer URL determinado, o método usará a instância fornecida para obter a client resposta como um byte[]arquivo . A CancellationToken instância é passada para os HttpClient.GetAsync(String, CancellationToken) métodos e HttpContent.ReadAsByteArrayAsync() . O token é usado para se registrar para cancelamento solicitado. O comprimento é retornado depois que o URL e o comprimento são gravados no console.

Exemplo de saída de aplicativo

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.

Exemplo completo

O código a seguir é o texto completo do arquivo Program.cs para o exemplo.

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

Consulte também

Próximos passos