Processar tarefas assíncronas à medida que são concluídas (C#)
Ao utilizar Task.WhenAnyo , pode iniciar múltiplas tarefas ao mesmo tempo e processá-las uma a uma à medida que são concluídas, em vez de processá-las pela ordem em que são iniciadas.
O exemplo seguinte utiliza uma consulta para criar uma coleção de tarefas. Cada tarefa transfere o conteúdo de um site especificado. Em cada iteração de um ciclo de tempo, uma chamada aguardada para WhenAny devolver a tarefa na coleção de tarefas que termina a transferência primeiro. Essa tarefa é removida da coleção e processada. O ciclo repete-se até que a coleção não contenha mais tarefas.
Pré-requisitos
Pode seguir este tutorial com uma das seguintes opções:
- Visual Studio 2022 com a carga de trabalho de desenvolvimento de ambiente de trabalho .NET instalada. O SDK .NET é instalado automaticamente quando seleciona esta carga de trabalho.
- O SDK .NET com um editor de código à sua escolha, como o Visual Studio Code.
Criar aplicação de exemplo
Crie uma nova aplicação de consola .NET Core. Pode criar uma com o comando da nova consola do dotnet ou a partir do Visual Studio.
Abra o ficheiro Program.cs no editor de código e substitua o código existente por este código:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Adicionar campos
Na definição de Program
classe, adicione os dois campos seguintes:
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 HttpClient
expõe a capacidade de enviar pedidos HTTP e receber respostas HTTP. O s_urlList
contém todos os URLs que a aplicação planeia processar.
Atualizar ponto de entrada da aplicação
O ponto de entrada principal na aplicação de consola é o Main
método . Substitua o método existente pelo seguinte:
static Task Main() => SumPageSizesAsync();
O método atualizado Main
é agora considerado um principal assíncrono, o que permite um ponto de entrada assíncrono no executável. É expresso como uma chamada para SumPageSizesAsync
.
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();
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
O while
ciclo remove uma das tarefas em cada iteração. Após a conclusão de cada tarefa, o ciclo termina. O método começa por instanciar e iniciar um Stopwatch. Em seguida, inclui uma consulta que, quando executada, cria uma coleção de tarefas. Cada chamada para ProcessUrlAsync
no seguinte código devolve um Task<TResult>, em que TResult
é um número inteiro:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Devido à execução diferida com o LINQ, chama Enumerable.ToList para iniciar cada tarefa.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
O while
ciclo executa os seguintes passos para cada tarefa na coleção:
Aguarda uma chamada para para
WhenAny
identificar a primeira tarefa na coleção que terminou a transferência.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Remove essa tarefa da coleção.
downloadTasks.Remove(finishedTask);
finishedTask
Aguarda , que é devolvido por uma chamada paraProcessUrlAsync
. AfinishedTask
variável é ondeTResult
Task<TResult> é um número inteiro. A tarefa já está concluída, mas aguarda-a para obter a duração do site transferido, como mostra o exemplo seguinte. Se a tarefa tiver falhas,await
apresentará a primeira exceção subordinada armazenada noAggregateException
, ao contrário de ler a Task<TResult>.Result propriedade , o que geraria oAggregateException
.total += await finishedTask;
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)
{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
Para qualquer URL especificado, o método utilizará a client
instância fornecida para obter a resposta como .byte[]
O comprimento é devolvido depois de o URL e o comprimento terem sido escritos na consola.
Execute o programa várias vezes para verificar se os comprimentos transferidos nem sempre aparecem pela mesma ordem.
Atenção
Pode utilizar WhenAny
num ciclo, conforme descrito no exemplo, para resolver problemas que envolvem um pequeno número de tarefas. No entanto, outras abordagens são mais eficientes se tiver um grande número de tarefas para processar. Para obter mais informações e exemplos, veja Processar tarefas à medida que são concluídas.
Exemplo completo
O código seguinte é o texto completo do ficheiro Program.cs do exemplo.
using System.Diagnostics;
HttpClient s_client = new()
{
MaxResponseContentBufferSize = 1_000_000
};
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"
};
await SumPageSizesAsync();
async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
// Example output:
// https://learn.microsoft.com 132,517
// https://learn.microsoft.com/powershell 57,375
// https://learn.microsoft.com/gaming 33,549
// https://learn.microsoft.com/aspnet/core 88,714
// https://learn.microsoft.com/surface 39,840
// https://learn.microsoft.com/enterprise-mobility-security 30,903
// https://learn.microsoft.com/microsoft-365 67,867
// https://learn.microsoft.com/windows 26,816
// https://learn.microsoft.com/maui 57,958
// https://learn.microsoft.com/dotnet 78,706
// https://learn.microsoft.com/graph 48,277
// https://learn.microsoft.com/dynamics365 49,042
// https://learn.microsoft.com/office 67,867
// https://learn.microsoft.com/system-center 42,887
// https://learn.microsoft.com/education 38,636
// https://learn.microsoft.com/azure 421,663
// https://learn.microsoft.com/visualstudio 30,925
// https://learn.microsoft.com/sql 54,608
// https://learn.microsoft.com/azure/devops 86,034
// Total bytes returned: 1,454,184
// Elapsed time: 00:00:01.1290403