L'idea alla base Parallel.ForEach()è che hai un set di thread e ogni thread elabora parte della raccolta. Come hai notato, questo non funziona con async- await, dove vuoi rilasciare il thread per la durata della chiamata asincrona.
Potresti "sistemarlo" bloccando i ForEach()thread, ma questo sconfigge l'intero punto di async- await.
Quello che potresti fare è usare TPL Dataflow invece di Parallel.ForEach(), che supporta Taskbene i asincroni .
In particolare, il tuo codice potrebbe essere scritto usando un TransformBlockche trasforma ogni id in un Customerusando il asynclambda. Questo blocco può essere configurato per l'esecuzione in parallelo. Collegheresti quel blocco a un oggetto ActionBlockche scrive ciascuno Customersulla console. Dopo aver configurato la rete a blocchi, è possibile Post()ciascun ID per il TransformBlock.
Nel codice:
var ids = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
var getCustomerBlock = new TransformBlock<string, Customer>(
async i =>
{
ICustomerRepo repo = new CustomerRepo();
return await repo.GetCustomer(i);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
});
var writeCustomerBlock = new ActionBlock<Customer>(c => Console.WriteLine(c.ID));
getCustomerBlock.LinkTo(
writeCustomerBlock, new DataflowLinkOptions
{
PropagateCompletion = true
});
foreach (var id in ids)
getCustomerBlock.Post(id);
getCustomerBlock.Complete();
writeCustomerBlock.Completion.Wait();
Anche se probabilmente vuoi limitare il parallelismo della TransformBlocka qualche piccola costante. Inoltre, è possibile limitare la capacità di TransformBlocke aggiungere gli elementi ad esso in modo asincrono utilizzando SendAsync(), ad esempio, se la raccolta è troppo grande.
Come ulteriore vantaggio rispetto al tuo codice (se ha funzionato) è che la scrittura inizierà non appena un singolo articolo è finito e non attendere fino al termine di tutta l'elaborazione.