Tengo una lista de tareas que creé así:
public async Task<IList<Foo>> 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 anterior :
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
Hace lo que quiero, pero esto parece bastante torpe. Prefiero escribir algo más simple como esto:
return tasks.Select(async task => await task).ToList();
... pero esto no se compila. ¿Qué me estoy perdiendo? ¿O simplemente no es posible expresar las cosas de esta manera?
c#
linq
async-await
Matt Johnson-Pinta
fuente
fuente

DoSomethingAsync(foo)serie para cada foo, o es un candidato para Parallel.ForEach <Foo> ?Parallel.ForEachestá bloqueando. El patrón aquí proviene del video Asynchronous C # de Jon Skeet en Pluralsight . Se ejecuta en paralelo sin bloquear..ToList()si solo lo voy a usarWhenAll)DoSomethingAsyncesté escrito, la lista puede o no ejecutarse en paralelo. Pude escribir un método de prueba que era y una versión que no lo era, pero en cualquier caso, el comportamiento lo dicta el método en sí, no el delegado que crea la tarea. Lo siento por la confusión. Sin embargo, siDoSomethingAsycregresaTask<Foo>, entonces elawaiten el delegado no es absolutamente necesario ... Creo que ese era el punto principal que iba a tratar de hacer.Respuestas:
LINQ no funciona perfectamente con
asynccódigo, pero puede hacer esto:var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks);Si todas sus tareas devuelven el mismo tipo de valor, incluso puede hacer esto:
var results = await Task.WhenAll(tasks);lo cual es bastante agradable.
WhenAlldevuelve una matriz, por lo que creo que su método puede devolver los resultados directamente:return await Task.WhenAll(tasks);fuente
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();var tasks = foos.Select(DoSomethingAsync).ToList();Select. Pero a la mayoría no les gustaWhere.asyncpara reducir subprocesos; si está vinculado a la CPU y ya está en un hilo de fondo, entoncesasyncno proporcionaría ningún beneficio.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<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } } }fuente
ToArrayAsyncUn problema con Task.WhenAll es que crearía un paralelismo. En la mayoría de los casos, puede que sea incluso mejor, pero a veces desea evitarlo. Por ejemplo, leer datos en lotes de DB y enviar datos a algún servicio web remoto. No desea cargar todos los lotes en la memoria, pero presione la base de datos una vez que se haya procesado el lote anterior. Entonces, debes romper la asincronicidad. 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 que .GetAwaiter (). GetResult () lo convierte en sincronizado. DB se golpearía de forma perezosa solo una vez que se haya procesado batchSize de eventos.
fuente
Use
Task.WaitAlloTask.WhenAlllo que sea apropiado.fuente
Task.WaitAllestá bloqueando, no se puede esperar y no funcionará con unTask<T>.WhenAll?Task.WhenAll debería hacer el truco aquí.
fuente