Ответ 1
Интересны асинхронные последовательности. Существует несколько разных подходов, в зависимости от того, что вы хотите сделать. Я не совсем понимаю вашу желаемую семантику, поэтому это некоторые из параметров.
Task<IEnumerable<T>>
- это асинхронно полученная коллекция. Существует только одна задача - одна асинхронная операция, которая извлекает всю коллекцию. Это не похоже на то, что вы хотите.
IEnumerable<Task<T>>
- (синхронная) последовательность (асинхронных) данных. Существует несколько задач, которые могут или не все могут обрабатываться одновременно. Есть несколько вариантов реализации этого. Один использует блок перечислителя и дает задания; этот подход начнет новую асинхронную операцию каждый раз, когда следующий элемент будет извлечен из перечислимого. Кроме того, вы можете создавать и возвращать коллекцию задач со всеми выполняемыми одновременно задачами (это можно сделать элегантно над исходной последовательностью через LINQ Select
, за которой следует ToList
/ToArray
). Тем не менее, это имеет несколько недостатков: нет способа асинхронно определять, завершена ли последовательность, и нелегко сразу начать следующую обработку элемента после возврата текущего элемента (что обычно является желательным поведением).
Основная проблема заключается в том, что IEnumerable<T>
по своей сути является синхронным. Есть несколько обходных решений. Один из них - IAsyncEnumerable<T>
, который является асинхронным эквивалентом IEnumerable<T>
и доступен в пакете Ix-Async NuGet. Однако этот подход имеет свои недостатки. Конечно, вы теряете хорошую поддержку языка для IEnumerable<T>
(а именно, блоков перечислителя и foreach
). Кроме того, само понятие "асинхронного перечислимого" не совсем соответствует действительности; в идеале, асинхронные API должны быть короткими, а не чатными, а перечисления - очень частыми. Больше обсуждений оригинального дизайна здесь, а на подробные/частые соображения здесь.
Итак, в наши дни гораздо более распространенным решением является использование наблюдаемых или потоки данных (оба доступны также через NuGet). В этих случаях вы должны думать о "последовательности" как о чем-то с собственной жизнью. Наблюдаемые данные основаны на нажатиях, поэтому код потребления (идеально) реагирует. В потоках данных есть ощущение актера, поэтому они действуют более независимыми, снова нажимая результаты на потребительский код.