Ответ 1
Я рекомендую вам не думать об этом как "используя async
в LINQ". Имейте в виду, что между ними: делегаты. Несколько операторов LINQ принимают делегатов, а async
можно использовать для создания асинхронного делегата.
Итак, когда у вас есть асинхронный метод BazAsync
, который возвращает Task
:
Task BazAsync(TBar bar);
то этот код приводит к последовательности задач:
IEnumerable<Task> tasks = bars.Select(bar => BazAsync(bar));
Аналогично, если вы используете async
и await
внутри делегата, вы создаете асинхронный делегат, который возвращает Task
:
IEnumerable<Task> tasks = bars.Select(async bar => await BazAsync(bar));
Эти два выражения LINQ функционально эквивалентны. Нет важных отличий.
Как и обычные выражения LINQ, IEnumerable<Task>
оценивается по лени. Только с помощью асинхронных методов, таких как BazAsync
, вам обычно не нужна случайная двойная оценка или что-то в этом роде. Итак, когда вы проецируете последовательность задач, обычно рекомендуется сразу же подтвердить последовательность. Это вызывает BazAsync
для всех элементов исходной последовательности, начиная все выполняемые задачи:
Task[] tasks = bars.Select(bar => BazAsync(bar)).ToArray();
Конечно, все, что мы сделали с помощью Select
, запускает асинхронную операцию для каждого элемента. Если вы хотите дождаться их завершения, используйте Task.WhenAll
:
await Task.WhenAll(tasks);
Большинство других операторов LINQ работают не так просто с асинхронными делегатами. Select
довольно просто: вы только начинаете асинхронную операцию для каждого элемента.