J'avais une telle méthode:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
foreach(var method in Methods)
{
string json = await Process(method);
result.Prop1 = PopulateProp1(json);
result.Prop2 = PopulateProp2(json);
}
return result;
}
J'ai alors décidé d'utiliser Parallel.ForEach
:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
Parallel.ForEach(Methods, async method =>
{
string json = await Process(method);
result.Prop1 = PopulateProp1(json);
result.Prop2 = PopulateProp2(json);
});
return result;
}
Mais maintenant j'ai une erreur:
Un module ou gestionnaire asynchrone s'est terminé alors qu'une opération asynchrone était toujours en attente.
async
ne fonctionne pas bien avec ForEach
. En particulier, votre async
lambda est en cours de conversion en async void
méthode. Il existe un certain nombre de raisons d'éviter async void
(comme je le décris dans un article MSDN); l'un d'eux est que vous ne pouvez pas facilement détecter quand le async
lambda est terminé. ASP.NET verra le retour de votre code sans avoir terminé le async void
méthode et (de manière appropriée) lève une exception.
Ce que vous voulez probablement faire, c'est traiter les données simultanément , mais pas en parallèle . Le code parallèle ne devrait presque jamais être utilisé sur ASP.NET. Voici à quoi ressemblerait le code avec un traitement simultané asynchrone:
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
var tasks = Methods.Select(method => ProcessAsync(method)).ToArray();
string[] json = await Task.WhenAll(tasks);
result.Prop1 = PopulateProp1(json[0]);
...
return result;
}
Alternativement, avec le AsyncEnumerator NuGet Package vous pouvez le faire:
using System.Collections.Async;
public async Task<MyResult> GetResult()
{
MyResult result = new MyResult();
await Methods.ParallelForEachAsync(async method =>
{
string json = await Process(method);
result.Prop1 = PopulateProp1(json);
result.Prop2 = PopulateProp2(json);
}, maxDegreeOfParallelism: 10);
return result;
}
où ParallelForEachAsync
est une méthode d'extension.
Ahh d'accord. Je pense que je sais ce qui se passe maintenant. async method =>
un "async void" qui est "tirer et oublier" (déconseillé pour autre chose que les gestionnaires d'événements). Cela signifie que l'appelant ne peut pas savoir quand il est terminé ... Ainsi, GetResult
retourne alors que l'opération est toujours en cours d'exécution. Bien que les détails techniques de ma première réponse soient incorrects, le résultat est le même ici: que GetResult renvoie alors que les opérations démarrées par ForEach
sont toujours en cours d'exécution. La seule chose que vous pourriez vraiment faire est de ne pas await
sur Process
(pour que le lambda ne soit plus async
) et d'attendre que Process
termine chaque itération . Mais, cela utilisera au moins un thread de pool de threads pour ce faire et stressera donc légèrement le pool - probablement en utilisant ForEach
inutile. Je n'utiliserais tout simplement pas Parallel.ForEach ...