¿Cómo esperar una lista de tareas de forma asincrónica utilizando LINQ?

Tengo una lista de tareas que he creado así:

public async Task<IList> GetFoosAndDoSomethingAsync() { var foos = await GetFoosAsync(); var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList(); ... } 

Al usar .ToList() , todas las tareas deberían comenzar. Ahora quiero esperar su finalización y devolver los resultados.

Esto funciona en el bloque de arriba ...

 var list = new List(); foreach (var task in tasks) list.Add(await task); return list; 

Hace lo que yo quiero, pero parece bastante torpe. Prefiero escribir algo más simple como este:

 return tasks.Select(async task => await task).ToList(); 

… pero esto no comstack. ¿Qué me estoy perdiendo? ¿O simplemente no es posible express las cosas de esta manera?

LINQ no funciona a la perfección con el código async , pero puedes hacer esto:

 var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks); 

Si todas sus tareas devuelven el mismo tipo de valor, puede incluso hacer esto:

 var results = await Task.WhenAll(tasks); 

que es bastante agradable. WhenAll devuelve una matriz, por lo que creo que su método puede devolver los resultados directamente:

 return await Task.WhenAll(tasks); 

Para ampliar la respuesta de Stephen, he creado el siguiente método de extensión para mantener el estilo fluido de LINQ. Entonces puedes hacer

 await someTasks.WhenAll() namespace System.Linq { public static class IEnumerableExtensions { public static Task WhenAll(this IEnumerable> source) { return Task.WhenAll(source); } } } 

Use Task.WaitAll o Task.WhenAll lo que sea apropiado.

Task.WhenAll debe hacer el truco aquí.

Un problema con Task.WhenAll es que crearía un paralelismo. En la mayoría de los casos, podría ser incluso mejor, pero a veces quieres evitarlo. Por ejemplo, leer datos en lotes desde DB y enviar datos a algún servicio web remoto. No desea cargar todos los lotes en la memoria, pero acceda al DB una vez que se haya procesado el lote anterior. Entonces, debes romper la asincronía. Aquí hay un ejemplo:

 var events = Enumerable.Range(0, totalCount/ batchSize) .Select(x => x*batchSize) .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult()) .SelectMany(x => x); foreach (var carEvent in events) { } 

Tenga en cuenta .GetAwaiter (). GetResult () convirtiéndolo a synchronose. DB sería golpeado flojamente solo una vez por lote. Se han procesado los tamaños de eventos.