¿Cómo puedo usar Async con ForEach?

¿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.

List.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:

  1. El manejo de errores es más apropiado. Las excepciones de async void no pueden capturarse con catch ; este enfoque propagará excepciones a la await Task.WhenAll línea, permitiendo el manejo de excepciones naturales.
  2. Sabes que las tareas están completas al final de este método, ya que await Task.WhenAll una await Task.WhenAll . Si utiliza un async void , no podrá saber fácilmente cuándo se han completado las operaciones.
  3. Este enfoque tiene una syntax 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.

Este pequeño método de extensión debería proporcionarle una iteración asíncrona segura de excepciones:

 public static async Task ForEachAsync(this List list, Func 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); }); 

Aquí hay una versión de trabajo real de las variantes asíncronas anteriores de foreach con procesamiento secuencial:

 public static async Task ForEachAsync(this List enumerable, Action 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 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); }); 

Agrega este método de extensión

 public static class ForEachAsyncExtension { public static Task ForEachAsync(this IEnumerable source, int dop, Func 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();