c# - whenall - ¿Cómo puedo usar Async con ForEach?
task httpresponsemessage c# (5)
Agrega este método de extensión
public static class ForEachAsyncExtension
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current).ConfigureAwait(false);
}));
}
}
Y luego usa así:
Task.Run(async () =>
{
var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
var buckets = await s3.ListBucketsAsync();
foreach (var s3Bucket in buckets.Buckets)
{
if (s3Bucket.BucketName.StartsWith("mybucket-"))
{
log.Information("Bucket => {BucketName}", s3Bucket.BucketName);
ListObjectsResponse objects;
try
{
objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
continue;
}
// ForEachAsync (4 is how many tasks you want to run in parallel)
await objects.S3Objects.ForEachAsync(4, async s3Object =>
{
try
{
log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
}
catch
{
log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
}
});
try
{
await s3.DeleteBucketAsync(s3Bucket.BucketName);
}
catch
{
log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
}
}
}
}).Wait();
¿Es posible utilizar Async cuando se usa ForEach? A continuación está el código que estoy intentando:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
Estoy obteniendo el error:
El nombre ''Async'' no existe en el contexto actual
El método en el que se incluye la instrucción using se establece en async.
Aquí hay una versión de trabajo real de las variantes asíncronas anteriores de foreach con procesamiento secuencial:
public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
foreach (var item in enumerable)
await Task.Run(() => { action(item); }).ConfigureAwait(false);
}
Aquí está la implementación:
public async void SequentialAsync()
{
var list = new List<Action>();
Action action1 = () => {
//do stuff 1
};
Action action2 = () => {
//do stuff 2
};
list.Add(action1);
list.Add(action2);
await list.ForEachAsync();
}
¿Cuál es la diferencia clave? .ConfigureAwait(false);
que mantiene el contexto del hilo principal mientras que el procesamiento secuencial asincrónico de cada tarea.
El problema era que la palabra clave async
debe aparecer antes de la lambda, no antes del cuerpo:
db.Groups.ToList().ForEach(async (i) => {
await GetAdminsFromGroup(i.Gid);
});
Este pequeño método de extensión debería proporcionarle una iteración asíncrona segura de excepciones:
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
Como estamos cambiando el tipo de devolución de lambda de void
a Task
, las excepciones se propagarán correctamente. Esto te permitirá escribir algo como esto en la práctica:
await db.Groups.ToList().ForEachAsync(async i => {
await GetAdminsFromGroup(i.Gid);
});
List<T>.ForEach
no funciona particularmente bien con async
(tampoco lo hace LINQ-to-objects, por las mismas razones).
En este caso, recomiendo proyectar cada elemento en una operación asincrónica, y luego puede (asincrónicamente) esperar a que todos se completen.
using (DataContext db = new DataLayer.DataContext())
{
var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
var results = await Task.WhenAll(tasks);
}
Los beneficios de este enfoque sobre dar un delegado async
a ForEach
son:
- El manejo de errores es más apropiado. Las excepciones de
async void
no pueden capturarse concatch
; este enfoque propagará excepciones a laawait Task.WhenAll
línea, permitiendo el manejo de excepciones naturales. - Sabes que las tareas están completas al final de este método, ya que
await Task.WhenAll
unaawait Task.WhenAll
. Si utiliza unasync void
, no podrá saber fácilmente cuándo se han completado las operaciones. - Este enfoque tiene una sintaxis natural para recuperar los resultados.
GetAdminsFromGroupAsync
suena como si fuera una operación que produce un resultado (los administradores), y tal código es más natural si tales operaciones pueden devolver sus resultados en lugar de establecer un valor como efecto secundario.