Skip to main content
๐Ÿ“œ WAYPOINT LESSON

async Streams: await foreach

โญโญ intermediateโณ 12 min read๐Ÿ“ Lesson 115 of 265

IAsyncEnumerable<T> for data that arrives over time, producing and consuming async sequences.

Producing: async iterator with yield return

async IAsyncEnumerable<int> PollAsync(CancellationToken ct = default)
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(10, ct);
        yield return i;
    }
}

yield return inside an async method makes an async iterator (.NET Core 3.0+). Items stream out as they're produced โ€” the consumer starts before the producer finishes.

Consuming: await foreach

await foreach (int item in PollAsync(ct))
    Console.WriteLine(item);

await foreach awaits each MoveNextAsync โ€” the loop suspends while waiting for the next item instead of blocking a thread.

Why not Task<List<T>>?

Task<List<T>> says "everything, eventually" โ€” the caller waits for the END before processing item one. IAsyncEnumerable<T> says "items over time" โ€” process as they arrive, cancel mid-stream, keep memory flat. Choose by arrival pattern, not style.

CancellationToken flows through

WithCancellation(ct) attaches the caller's token to the stream; inside the producer, honor the token you were given. The [EnumeratorCancellation] attribute on the default parameter merges caller and local tokens โ€” the pattern used by the BCL.

Check your understanding

  • Which type for a log tail? (IAsyncEnumerable โ€” items arrive over time.)
  • What does await foreach await? (Each item's availability.)