A idéia por trás Parallel.ForEach()disso é que você tem um conjunto de threads e cada thread processa parte da coleção. Como você notou, isso não funciona com async- await, no qual você deseja liberar o encadeamento pela duração da chamada assíncrona.
Você pode "consertar" isso bloqueando os ForEach()threads, mas isso derrota todo o ponto de async- await.
O que você pode fazer é usar o TPL Dataflow em vez de Parallel.ForEach(), que suporta Taskbem os assíncronos .
Especificamente, seu código pode ser escrito usando um TransformBlockque transforma cada ID em um Customerusando o asynclambda. Este bloco pode ser configurado para ser executado em paralelo. Você vincularia esse bloco a um ActionBlockque grave cada um Customerno console. Depois de configurar a rede de bloqueio, Post()cada um pode identificar o TransformBlock.
Em código:
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();
Embora você provavelmente queira limitar o paralelismo da a TransformBlockuma pequena constante. Além disso, você pode limitar a capacidade do TransformBlocke adicionar itens a ele de forma assíncrona SendAsync(), por exemplo, se a coleção for muito grande.
Como um benefício adicional quando comparado ao seu código (se funcionou) é que a gravação começará assim que um único item for concluído e não espere até que todo o processamento esteja concluído.