Est-il possible d'utiliser Async en utilisant ForEach? Voici le code que j'essaye:
using (DataContext db = new DataLayer.DataContext())
{
db.Groups.ToList().ForEach(i => async {
await GetAdminsFromGroup(i.Gid);
});
}
Je reçois l'erreur:
Le nom 'Async' n'existe pas dans le contexte actuel
La méthode dans laquelle l'instruction using est incluse est définie sur async.
List<T>.ForEach
ne joue pas très bien avec async
(ni LINQ-to-objets, pour les mêmes raisons).
Dans ce cas, je recommande projection chaque élément dans une opération asynchrone, et vous pouvez ensuite (de manière asynchrone) attendre qu'ils soient tous terminés.
using (DataContext db = new DataLayer.DataContext())
{
var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
var results = await Task.WhenAll(tasks);
}
Les avantages de cette approche par rapport à l'attribution d'un délégué async
à ForEach
sont les suivants:
async void
ne peut pas être attrapé avec catch
; cette approche propagera des exceptions au await Task.WhenAll
_, permettant la gestion naturelle des exceptions.await Task.WhenAll
. Si tu utilises async void
, vous ne pouvez pas facilement savoir quand les opérations sont terminées.GetAdminsFromGroupAsync
ressemble à une opération qui produit un résultat (les administrateurs), et ce code est plus naturel si de telles opérations peuvent retourner leurs résultats plutôt que de définir une valeur en tant qu'effet secondaire.Cette petite méthode d'extension devrait vous donner une itération asynchrone sans exception:
public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
foreach (var value in list)
{
await func(value);
}
}
Comme nous modifions le type de retour du lambda de void
à Task
, les exceptions se propagent correctement. Cela vous permettra d’écrire quelque chose comme ceci dans la pratique:
await db.Groups.ToList().ForEachAsync(async i => {
await GetAdminsFromGroup(i.Gid);
});
Voici une version de travail de la variante asynchrone ci-dessus pour chaque variante avec traitement séquentiel:
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);
}
Voici l'implémentation:
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();
}
Quelle est la différence clé? .ConfigureAwait(false);
qui conserve le contexte du thread principal lors du traitement séquentiel asynchrone de chaque tâche.
Le problème était que le mot clé async
devait apparaître avant le lambda, pas avant le corps:
db.Groups.ToList().ForEach(async (i) => {
await GetAdminsFromGroup(i.Gid);
});
Ajouter cette méthode d'extension
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);
}));
}
}
Et puis utilisez comme si:
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();
La réponse simple consiste à utiliser le mot clé foreach
à la place de la méthode ForEach()
de List()
.
using (DataContext db = new DataLayer.DataContext())
{
foreach(var i in db.Groups)
{
await GetAdminsFromGroup(i.Gid);
}
}