IAsyncEnumerable and await foreach in C#: streaming data, EnumeratorCancellation and EF Core

C# Async & Multithreading November 13, 2026 csharp

When data arrives gradually and you don't want to load it all into memory: exports, large queries, message consumers, paging through an external API.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ======================================================================
// Q: IAsyncEnumerable and await foreach: when and how?
//
// When data arrives gradually and you don't want to load it all into memory:
// exports, large queries, message consumers, paging through an external API.
// - [EnumeratorCancellation] is required for .WithCancellation(ct) to work.
// - An IAsyncEnumerable from EF Core keeps the connection open while you read.
//   Don't make slow calls inside the loop.
// - In ASP.NET Core, an action that returns IAsyncEnumerable<T> streams JSON
//   (demo: GET /api/orders/export).
// ======================================================================

public async IAsyncEnumerable<ProductDto> GetAllFromPimAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    string? cursor = null;
    do
    {
        var page = await pim.GetProductsAsync(cursor, ct);
        foreach (var p in page.Items) yield return p;    // the consumer gets the first items before all of them arrive
        cursor = page.NextCursor;
    } while (cursor is not null);
}

await foreach (var p in GetAllFromPimAsync().WithCancellation(ct)) { /* ... */ }