Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Ao usar Task.WhenAny, você pode iniciar várias tarefas ao mesmo tempo e processá-las uma a uma, pois elas são concluídas em vez de processá-las na ordem em que elas são iniciadas.
O exemplo a seguir usa uma consulta para criar uma coleção de tarefas. Cada tarefa baixa o conteúdo de um site especificado. Em cada iteração de um loop de tempo, uma chamada aguardada para WhenAny retornar a tarefa na coleção de tarefas que conclui seu download primeiro. Essa tarefa é removida da coleção e processada. O loop é repetido até que a coleção não contenha mais tarefas.
Pré-requisitos
Você pode seguir este tutorial usando uma das seguintes opções:
- Visual Studio 2022 com a carga de trabalho Desenvolvimento de área de trabalho do .NET instalada. O SDK do .NET é instalado automaticamente quando você seleciona essa carga de trabalho.
- O SDK do .NET com um editor de código de sua escolha, como o Visual Studio Code.
Criar aplicativo de exemplo
Crie um novo aplicativo de console do .NET Core. Você pode criar um usando o novo comando de console do dotnet ou do Visual Studio.
Abra o arquivo 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 a seguir:
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"
};
Expõe HttpClient a capacidade de enviar solicitações HTTP e receber respostas HTTP. Contém s_urlList 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 Task Main() => SumPageSizesAsync();
O método atualizado Main agora é considerado um principal assíncrono, que permite um ponto de entrada assíncrono no executável. Ele é 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 loop remove uma das tarefas em cada iteração. Depois que cada tarefa for concluída, o loop terminará. O método começa instanciando e iniciando um Stopwatch. Em seguida, ele inclui uma consulta que, quando executada, cria uma coleção de tarefas. Cada chamada para ProcessUrlAsync o código a seguir retorna um Task<TResult>, em que TResult está um inteiro:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Devido à execução adiada com o LINQ, você chama Enumerable.ToList para iniciar cada tarefa.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
O while loop executa as seguintes etapas para cada tarefa na coleção:
Aguarda uma chamada para
WhenAnyidentificar a primeira tarefa na coleção que concluiu seu download.Task<int> finishedTask = await Task.WhenAny(downloadTasks);Remove essa tarefa da coleção.
downloadTasks.Remove(finishedTask);finishedTaskAguarda, que é retornado por uma chamada paraProcessUrlAsync. AfinishedTaskvariável é um Task<TResult> localTResultem que é um inteiro. A tarefa já está concluída, mas você a aguarda para recuperar o comprimento do site baixado, como mostra o exemplo a seguir. Se a tarefa tiver falha,awaitgerará a primeira exceção filho armazenada no , ao contrário daAggregateExceptionleitura da Task<TResult>.Result propriedade, que lançaria 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 fornecida, o método usará a client instância fornecida para obter a resposta como um byte[]. O comprimento é retornado depois que a URL e o comprimento são gravados no console.
Execute o programa várias vezes para verificar se os comprimentos baixados nem sempre aparecem na mesma ordem.
Cuidado
Você pode usar WhenAny em um loop, conforme descrito no exemplo, para resolver problemas que envolvem um pequeno número de tarefas. No entanto, outras abordagens serão mais eficientes se você tiver um grande número de tarefas a serem processadas. Para obter mais informações e exemplos, consulte As tarefas de processamento à medida que elas são concluídas.
Simplificar a abordagem usando Task.WhenEach
O while loop implementado no SumPageSizesAsync método pode ser simplificado usando o novo Task.WhenEach método introduzido no .NET 9 chamando-o em await foreach loop.
Substitua o loop implementado while anteriormente:
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
com o simplificado await foreach:
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
Essa nova abordagem permite não chamar Task.WhenAny mais repetidamente para chamar manualmente uma tarefa e remover a que termina, pois Task.WhenEach itera por meio da tarefa em uma ordem de conclusão.
Exemplo completo
O código a seguir é o texto completo do arquivo Program.cs para o 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