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:
- SDK do .NET 5 ou posterior
- Ambiente de desenvolvimento integrado (IDE)
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 cancelTask
sumPageSizeTask
é 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.Token
ProcessUrlAsync
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;
}
}