Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Task.WhenAnyMediante , puede iniciar varias tareas al mismo tiempo y procesarlas una por una a medida que se completan en lugar de procesarlas en el orden en que se inician.
En el ejemplo siguiente se usa una consulta para crear una colección de tareas. Cada tarea descarga el contenido de un sitio web especificado. En cada iteración de un bucle while, una llamada esperada a WhenAny devuelve la tarea en la colección de tareas que finaliza su descarga en primer lugar. Esa tarea se quita de la colección y se procesa. El bucle se repite hasta que la colección no contiene más tareas.
Prerrequisitos
Puede seguir este tutorial mediante una de las siguientes opciones:
- Visual Studio 2022 con la carga de trabajo de desarrollo de escritorio de .NET instalada. El SDK de .NET se instala automáticamente al seleccionar esta carga de trabajo.
- El SDK de .NET con un editor de código que prefiera, como Visual Studio Code.
Creación de una aplicación de ejemplo
Cree una nueva aplicación de consola de .NET Core. Puede crear uno mediante el comando dotnet new console o desde Visual Studio.
Abra el archivo Program.cs en el editor de código y reemplace el código existente por este código:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Adición de campos
En la definición de clase Program , agregue los dos campos siguientes:
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"
};
HttpClient expone la capacidad de enviar solicitudes HTTP y recibir respuestas HTTP.
s_urlList contiene todas las direcciones URL que la aplicación planea procesar.
Actualizar el punto de entrada de la aplicación
El punto de entrada principal en la aplicación de consola es el Main método . Reemplace el método existente por lo siguiente:
static Task Main() => SumPageSizesAsync();
El método actualizado Main ahora se considera un elemento principal asincrónico, que permite un punto de entrada asincrónico en el ejecutable. Se expresa como una llamada a SumPageSizesAsync.
Creación del método de tamaños de página de suma asincrónica
Debajo del Main método , agregue el 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");
}
El while bucle quita una de las tareas de cada iteración. Una vez completada cada tarea, el bucle finaliza. El método comienza creando instancias e iniciando un Stopwatch. A continuación, incluye una consulta que, cuando se ejecuta, crea una colección de tareas. Cada llamada a ProcessUrlAsync en el código siguiente devuelve un Task<TResult>, donde TResult es un entero:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Debido a la ejecución diferida con LINQ, se llama Enumerable.ToList a para iniciar cada tarea.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
El while bucle realiza los pasos siguientes para cada tarea de la colección:
Espera una llamada a para
WhenAnyidentificar la primera tarea de la colección que ha finalizado su descarga.Task<int> finishedTask = await Task.WhenAny(downloadTasks);Quita esa tarea de la colección.
downloadTasks.Remove(finishedTask);finishedTaskEspera , que devuelve una llamada aProcessUrlAsync. LafinishedTaskvariable es un Task<TResult> dondeTResultes un entero. La tarea ya está completa, pero espera que recupere la longitud del sitio web descargado, como se muestra en el ejemplo siguiente. Si se produce un error en la tarea,awaitse producirá la primera excepción secundaria almacenada enAggregateException, a diferencia de leer la Task<TResult>.Result propiedad , que produciría .AggregateExceptiontotal += await finishedTask;
Agregar método de proceso
Agregue el método siguiente ProcessUrlAsync debajo del 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 cualquier dirección URL determinada, el método usará la client instancia proporcionada para obtener la respuesta como .byte[] La longitud se devuelve después de escribir la dirección URL y la longitud en la consola.
Ejecute el programa varias veces para comprobar que las longitudes descargadas no siempre aparecen en el mismo orden.
Precaución
Puede usar WhenAny en un bucle, como se describe en el ejemplo, para resolver problemas que implican un pequeño número de tareas. Sin embargo, otros enfoques son más eficaces si tiene un gran número de tareas que procesar. Para obtener más información y ejemplos, vea Procesamiento de tareas a medida que se completan.
Simplificación del enfoque mediante Task.WhenEach
El while bucle implementado en SumPageSizesAsync el método se puede simplificar mediante el nuevo Task.WhenEach método introducido en .NET 9 llamando a él en await foreach bucle.
Reemplace el bucle implementado while anteriormente:
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
con el simplificado await foreach:
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
Este nuevo enfoque ya no permite llamar Task.WhenAny repetidamente a para llamar manualmente a una tarea y quitar el que finaliza, ya Task.WhenEach que recorre en iteración la tarea en un orden de su finalización.
Ejemplo completo
El código siguiente es el texto completo del archivo Program.cs para el ejemplo.
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