Parallel.ForEach e async-await

Ho avuto un tale metodo:

public async Task 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; } 

Quindi ho deciso di utilizzare Parallel.ForEach :

 public async Task 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; } 

Ma ora ho un errore:

Un modulo o un handler asincrono è stato completato mentre era ancora in sospeso un’operazione asincrona.

async non funziona bene con ForEach . In particolare, il tuo lambda async viene convertito in un metodo di async void . Ci sono una serie di motivi per evitare il async void (come descrivo in un articolo di MSDN); uno di questi è che non è ansible rilevare facilmente quando il lambda async ha completato. ASP.NET vedrà il tuo codice restituire senza completare il metodo del async void e (appropriatamente) lanciare un’eccezione.

Quello che probabilmente vorrai fare è elaborare i dati contemporaneamente , ma non in parallelo . Il codice parallelo non dovrebbe quasi mai essere utilizzato su ASP.NET. Ecco come apparirà il codice con l’elaborazione simultanea asincrona:

 public async Task 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; } 

Ah, ok. Penso di sapere cosa sta succedendo ora. async method => un “async void” che è “fire and forget” (non consigliato per qualcosa di diverso dai gestori di eventi). Ciò significa che il chiamante non può sapere quando è completato … Quindi, GetResult ritorna mentre l’operazione è ancora in esecuzione. Sebbene i dettagli tecnici della mia prima risposta non siano corretti, il risultato è lo stesso qui: che GetResult sta tornando mentre le operazioni avviate da ForEach sono ancora in esecuzione. L’unica cosa che potresti davvero fare non è await su Process (in modo che lambda non sia più async ) e attendere che Process completi ogni iterazione. Ma, per farlo, userà almeno un thread thread threading e quindi stresserà leggermente il pool, probabilmente facendo uso di ForEach senza senso. Semplicemente non userei Parallel.ForEach …

In alternativa, con il pacchetto NuGet di AsyncEnumerator puoi farlo:

 using System.Collections.Async; public async Task 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; } 

dove ParallelForEachAsync è un metodo di estensione.