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.ForEach
está 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
)DoSomethingAsync
esté 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, siDoSomethingAsyc
regresaTask<Foo>
, entonces elawait
en 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
async
có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.
WhenAll
devuelve 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
.async
para reducir subprocesos; si está vinculado a la CPU y ya está en un hilo de fondo, entoncesasync
no 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
ToArrayAsync
Un 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.WaitAll
oTask.WhenAll
lo que sea apropiado.fuente
Task.WaitAll
está bloqueando, no se puede esperar y no funcionará con unTask<T>
.WhenAll
?Task.WhenAll debería hacer el truco aquí.
fuente