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 Task
bene i asincroni .
In particolare, il tuo codice potrebbe essere scritto usando un TransformBlock
che trasforma ogni id in un Customer
usando il async
lambda. Questo blocco può essere configurato per l'esecuzione in parallelo. Collegheresti quel blocco a un oggetto ActionBlock
che scrive ciascuno Customer
sulla 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 TransformBlock
a qualche piccola costante. Inoltre, è possibile limitare la capacità di TransformBlock
e 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.