0Pricing
C# Academy · Урок

IAsyncEnumerable и await foreach

Асинхронно передавайте данные потоками с помощью IAsyncEnumerable, используйте yield в асинхронных итераторах и получайте данные через await foreach.

«IAsyncEnumerable и await foreach» — бесплатный урок C# Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Зачем нужен IAsyncEnumerable?

IAsyncEnumerable<T> (представленный в C# 8 и .NET Core 3) обеспечивает асинхронную потоковую передачу: элементы выдаются по одному по мере готовности, без предварительной загрузки всего набора в память. Например, можно построчно читать курсор базы данных или потоково получать ответы API.

Написание асинхронного итератора

Объявите метод, возвращающий IAsyncEnumerable<T>, и используйте yield return внутри async-метода. Ключевые слова await и yield return естественно сочетаются.

public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(100); // simulate async work per item
        yield return i;
    }
}

Использование await foreach

Обрабатывайте IAsyncEnumerable<T> с помощью await foreach. Каждая итерация асинхронно ожидает следующий элемент — consumer и producer естественным образом задают темп друг другу.

await foreach (var number in GenerateNumbersAsync(10))
{
    Console.WriteLine(number);
}

// With cancellation:
var cts = new CancellationTokenSource();
await foreach (var n in GenerateNumbersAsync(100)
    .WithCancellation(cts.Token))
{
    if (n > 50) { cts.Cancel(); break; }
    Console.WriteLine(n);
}

Потоковое чтение из базы данных

EF Core поддерживает потоковое получение результатов запросов с помощью AsAsyncEnumerable(). Строки читаются по одной, без загрузки всего набора результатов в память — это особенно важно для больших таблиц.

public async IAsyncEnumerable<Product> StreamProductsAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await foreach (var product in _db.Products
        .Where(p => p.IsActive)
        .AsAsyncEnumerable()
        .WithCancellation(ct))
    {
        yield return product;
    }
}

Потоковая передача HTTP-ответов

Передавайте большие ответы клиентам без буферизации всего содержимого. Клиент получает данные постепенно, пока сервер продолжает обработку.

app.MapGet("/stream/products", (ProductRepository repo) =>
{
    // Returns an IAsyncEnumerable — ASP.NET Core streams it as JSON array
    return repo.StreamAllAsync();
});

// Or with custom serialization:
app.MapGet("/stream/events", async (HttpResponse response, EventStore store) =>
{
    response.ContentType = "application/json";
    await foreach (var e in store.StreamAsync())
    {
        await response.WriteAsJsonAsync(e);
        await response.Body.FlushAsync();
    }
});

Атрибут EnumeratorCancellation

Примените [EnumeratorCancellation] к параметру CancellationToken асинхронного итератора, чтобы WithCancellation() на стороне consumer передавал этот токен producer.

public async IAsyncEnumerable<LogEntry> TailLogAsync(
    string logFile,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    while (!ct.IsCancellationRequested)
    {
        var newLines = await ReadNewLinesAsync(logFile, ct);
        foreach (var line in newLines)
            yield return new LogEntry(line);
        await Task.Delay(500, ct);
    }
}

// Consumer:
await foreach (var entry in TailLogAsync("/var/log/app.log")
    .WithCancellation(cts.Token))
{
    Console.WriteLine(entry.Message);
}

LINQ для IAsyncEnumerable

Пакет NuGet System.Linq.Async предоставляет асинхронные операторы LINQ (WhereAwait, SelectAwait, ToListAsync) для IAsyncEnumerable.

// dotnet add package System.Linq.Async

var expensiveProducts = await repo.StreamAllAsync()
    .WhereAwait(async p => await pricing.IsExpensiveAsync(p.Id))
    .SelectAwait(async p => await enricher.EnrichAsync(p))
    .Take(10)
    .ToListAsync();

// Or use built-in Where/Select that don't need async:
var names = repo.StreamAllAsync()
    .Where(p => p.IsActive)
    .Select(p => p.Name);

Объединение нескольких асинхронных потоков

Чтобы одновременно обрабатывать несколько асинхронных потоков, используйте Channel как точку объединения: несколько producers записывают данные, а один consumer их читает.

async IAsyncEnumerable<T> Merge<T>(
    params IAsyncEnumerable<T>[] streams)
{
    var channel = Channel.CreateUnbounded<T>();

    var producers = streams.Select(async stream =>
    {
        await foreach (var item in stream)
            await channel.Writer.WriteAsync(item);
    });

    _ = Task.WhenAll(producers)
            .ContinueWith(_ => channel.Writer.Complete());

    await foreach (var item in channel.Reader.ReadAllAsync())
        yield return item;
}

Производительность: буферизация и потоковая обработка

При обработке больших наборов данных всегда используйте потоковую обработку. Предварительная загрузка всего набора в список (ToListAsync()) одновременно удерживает все данные в памяти, тогда как потоковая обработка обрабатывает по одному элементу.

// BAD: loads all 1M records into memory
var allOrders = await _db.Orders.ToListAsync();
foreach (var order in allOrders)
    await ProcessAsync(order);

// GOOD: processes one record at a time
await foreach (var order in _db.Orders.AsAsyncEnumerable())
    await ProcessAsync(order);
// Memory usage: O(1) vs O(n)

Обработка ошибок в асинхронных итераторах

Оберните весь await foreach в try/catch, чтобы обрабатывать ошибки producer. Ошибки итератора передаются consumer в текущей точке итерации.

try
{
    await foreach (var item in RiskyStreamAsync())
    {
        await ProcessAsync(item);
    }
}
catch (HttpRequestException ex)
{
    Console.Error.WriteLine($"Stream error: {ex.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Stream cancelled.");
}

Быстрая проверка

В чём главное преимущество IAsyncEnumerable перед возвратом List с точки зрения использования памяти?

Повторение: IAsyncEnumerable и await foreach

Основные выводы:

  • IAsyncEnumerable<T>: асинхронно передаёт элементы по одному
  • yield return в async-методах создаёт асинхронные итераторы
  • await foreach: обрабатывает поток с отменой через .WithCancellation()
  • [EnumeratorCancellation]: передаёт токен consumer в итератор
  • EF Core: используйте AsAsyncEnumerable() для экономичной по памяти обработки больших запросов
  • При обработке больших наборов данных используйте потоковую передачу вместо ToListAsync()

Часто задаваемые вопросы

Урок «IAsyncEnumerable и await foreach» бесплатный?

Да — полный текст урока «IAsyncEnumerable и await foreach» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «IAsyncEnumerable и await foreach»?

Асинхронно передавайте данные потоками с помощью IAsyncEnumerable, используйте yield в асинхронных итераторах и получайте данные через await foreach. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «IAsyncEnumerable и await foreach»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. IAsyncEnumerable и await foreach
  2. System.Threading.Channels
  3. ValueTask и предотвращение выделений памяти
  4. ConfigureAwait и контекст синхронизации
← Назад к C# Academy