Вся ідея Parallel.ForEach()
полягає в тому, що у вас є набір потоків, і кожен потік обробляє частину колекції. Як ви помітили, це не працює з async
- await
, де ви хочете звільнити потік протягом тривалості виклику асинхронізації.
Ви можете це "виправити", блокуючи ForEach()
нитки, але це перемагає всю точку async
- await
.
Що ви можете зробити, це використовувати TPL Dataflow замість Parallel.ForEach()
, який підтримує асинхронний Task
s добре.
Зокрема, ваш код може бути записаний за допомогою символу a, TransformBlock
який перетворює кожен ідентифікатор у Customer
використовуваний async
лямбда. Цей блок можна налаштувати паралельно виконувати. Ви б зв'язали цей блок із тим, ActionBlock
що пише кожен Customer
на консоль. Після налаштування блокової мережі Post()
кожен ідентифікатор можна перейти до TransformBlock
.
У коді:
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();
Хоча ви, мабуть, хочете обмежити паралелізм TransformBlock
на деякій невеликій постійній. Крім того, ви можете обмежити ємність TransformBlock
та додати до неї елементи асинхронно, використовуючи SendAsync()
, наприклад, якщо колекція занадто велика.
В якості додаткової переваги в порівнянні з вашим кодом (якщо він працював) є те, що запис розпочнеться, як тільки один елемент буде закінчено, а не чекати, поки вся обробка завершена.