OOP Course
Сьогодні

Підрозділ 17.6

Асинхронні потоки. IAsyncEnumerable<T>

17.6. Асинхронні потоки. IAsyncEnumerable<T Класичний async Task<T відповідає на одне питання: «дай мені результат асинхронно». Але що, якщо результатів багато і вони надходять поступово — рядки з великого файл

17.6. Асинхронні потоки. IAsyncEnumerable

Класичний async Task<T> відповідає на одне питання: «дай мені результат асинхронно». Але що, якщо результатів багато і вони надходять поступово — рядки з великого файлу, повідомлення з черги, дані з БД мільйонами рядків? Завантажити все у пам'ять і повернути Task<List<T>> — неефективно або неможливо. Саме для цього C# 8.0 ввів IAsyncEnumerable<T>асинхронні потоки даних, де кожен елемент може бути отриманий асинхронно.

Блокуюче vs стрімінгове завантаження

Проблема: завантажити все vs обробляти по мірі надходження

Порівняємо два підходи для завантаження великого набору медичних записів:

При IAsyncEnumerable<T> перший елемент доступний через ~50 мс, а не через 250 мс. Користувач бачить результати миттєво — це принципова різниця у сприйнятті швидкості.

Оголошення та yield return

Метод, що повертає IAsyncEnumerable<T>, використовує:

  • async у заголовку
  • IAsyncEnumerable<T> як тип повернення
  • yield return для кожного елемента
  • await між елементами для асинхронних операцій

yield return у async-методі — це точка, де метод «призупиняється» і повертає елемент споживачу. Споживач обробляє елемент, і лише потім метод продовжує генерувати наступний.

await foreach — споживання асинхронного потоку

await foreach — спеціальна синтаксична конструкція для обходу IAsyncEnumerable<T>. Вона асинхронно очікує кожен наступний елемент і виконує тіло циклу:

Компілятор перетворює await foreach на виклики GetAsyncEnumerator(), MoveNextAsync() та Current — async-аналоги синхронного IEnumerable<T>.

CancellationToken у асинхронних потоках

IAsyncEnumerable<T> підтримує скасування через атрибут [EnumeratorCancellation] у параметрах методу-генератора. Це дозволяє передавати токен через WithCancellation() при await foreach:

Без [EnumeratorCancellation] токен, переданий через WithCancellation(), не потрапить у параметри методу-генератора і не спрацює.

Фільтрація та трансформація потоку

Асинхронні потоки можна обробляти у pipeline: один метод генерує, інший фільтрує або трансформує. Це async-аналог LINQ-ланцюгів для синхронних колекцій:

Кожен метод у pipeline є незалежним async-генератором, що не зберігає всі дані у пам'яті — передає елемент далі одразу при отриманні.

IAsyncEnumerable vs інші підходи

Підхід Повертає Споживання Зберігає в пам'яті Підходить для
Task<List<T>> Весь список одразу await + foreach Весь список Малі набори даних
IEnumerable<T> Елементи по одному foreach Один елемент Синхронна генерація
IAsyncEnumerable<T> Елементи по одному await foreach Один елемент Async-генерація, стрімінг
Channel<T> Черга повідомлень ReadAllAsync() Буфер Продюсер-споживач

IAsyncEnumerable<T> — правильний вибір, коли:

  • Джерело даних велике або нескінченне (БД-курсор, черга, сенсорний потік)
  • Перший результат потрібен якнайшвидше
  • Продюсер і споживач можуть мати різну швидкість
  • Обробка кожного елемента асинхронна

Практичний приклад: стрімінг пацієнтів з БД

Цей патерн — «стрімінг репозиторій» — є стандартом для роботи з великими наборами даних у сучасних .NET-застосунках. IAsyncEnumerable<T> підтримується у Entity Framework Core через ToAsyncEnumerable(), у gRPC-стрімінгу, у SignalR і у всій екосистемі .NET.

Розроблено Tomka Yurii · © 2026 ·