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 Task
bem os assíncronos .
Especificamente, seu código pode ser escrito usando um TransformBlock
que transforma cada ID em um Customer
usando o async
lambda. Este bloco pode ser configurado para ser executado em paralelo. Você vincularia esse bloco a um ActionBlock
que grave cada um Customer
no 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 TransformBlock
uma pequena constante. Além disso, você pode limitar a capacidade do TransformBlock
e 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.