List<T>.ForEach
ne joue pas particulièrement bien avec async
(LINQ-to-objects non plus, pour les mêmes raisons).
Dans ce cas, je recommande de projeter chaque élément dans une opération asynchrone, et vous pouvez ensuite (de manière asynchrone) attendre qu'ils se terminent tous.
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 au fait de donner à un async
délégué ForEach
sont les suivants:
- La gestion des erreurs est plus appropriée. Les exceptions à
async void
ne peuvent pas être prises avec catch
; cette approche propagera les exceptions à la await Task.WhenAll
ligne, permettant une gestion naturelle des exceptions.
- Vous savez que les tâches sont terminées à la fin de cette méthode, puisqu'elle fait un
await Task.WhenAll
. Si vous utilisez async void
, vous ne pouvez pas facilement dire quand les opérations sont terminées.
- Cette approche a une syntaxe naturelle pour récupérer les résultats.
GetAdminsFromGroupAsync
ressemble à une opération qui produit un résultat (les administrateurs), et un tel code est plus naturel si de telles opérations peuvent renvoyer leurs résultats plutôt que de définir une valeur comme effet secondaire.
List.ForEach()
ne fait pas partie de LINQ.async
. Ils ont été très utiles!foreach
avec unawait
corps dans votre boucle.ForEach
prend uniquement un type de délégué synchrone, et il n'y a pas de surcharge prenant un type de délégué asynchrone. Donc, la réponse courte est "personne n'a écrit un asynchroneForEach
". La réponse la plus longue est que vous devez assumer une certaine sémantique; Par exemple, les éléments doivent-ils être traités un à la fois (commeforeach
) ou simultanément (commeSelect
)? Si un à la fois, les flux asynchrones ne seraient-ils pas une meilleure solution? Si simultanément, les résultats doivent-ils être dans l'ordre original de l'article ou dans l'ordre d'achèvement? Doit-il échouer au premier échec ou attendre que tout soit terminé? Etc.SemaphoreSlim
pour limiter les tâches asynchrones.Cette petite méthode d'extension devrait vous donner une itération asynchrone sans exception:
Puisque nous changeons le type de retour du lambda de
void
àTask
, les exceptions se propageront correctement. Cela vous permettra d'écrire quelque chose comme ça dans la pratique:la source
async
devrait être avanti =>
ForEachAsync
s'agit essentiellement d'une méthode de bibliothèque, donc l'attente devrait probablement être configurée avecConfigureAwait(false)
.La réponse simple est d'utiliser le
foreach
mot - clé au lieu de laForEach()
méthode deList()
.la source
Voici une version de travail réelle des variantes async foreach ci-dessus avec traitement séquentiel:
Voici la mise en œuvre:
Quelle est la principale différence?
.ConfigureAwait(false);
qui conserve le contexte du thread principal pendant le traitement séquentiel asynchrone de chaque tâche.la source
En commençant par
C# 8.0
, vous pouvez créer et consommer des flux de manière asynchrone.Plus
la source
MoveNext
de l'énumérateur. Ceci est important dans les cas où l'énumérateur ne peut pas récupérer l'élément suivant instantanément et doit attendre qu'il soit disponible.Ajouter cette méthode d'extension
Et puis utilisez comme ceci:
la source
Le problème était que le
async
mot - clé doit apparaître avant le lambda, pas avant le corps:la source
async void
. Cette approche a des problèmes autour de la gestion des exceptions et de savoir quand les opérations asynchrones se terminent.