IAsyncEnumerable<T> represents a sequence whose next value may require asynchronous work. It parallels IEnumerable<T>, but GetAsyncEnumerator returns an IAsyncEnumerator<T>; MoveNextAsync returns ValueTask<bool>, Current returns the value, and DisposeAsync releases the enumerator. The enumerator factory itself should return quickly, deferring lengthy initialization until MoveNextAsync. section 14.1
Consume an asynchronous sequence with await foreach. Conceptually, the compiler obtains an async enumerator, awaits MoveNextAsync on each iteration, reads Current, and asynchronously disposes the enumerator. This preserves the shape of ordinary foreach while allowing retrieval or generation of each item to suspend without blocking. section 14.1 section 14.8
Generate values asynchronously
private async IAsyncEnumerable<int> AsyncYieldDemo() { yield return 1; await Task.Delay(1000); yield return 2; } public async Task UseAsyncYieldDemo() { await foreach (var current in AsyncYieldDemo()) Console.WriteLine($"Got {current}"); } The compiler combines iterator and async transformations, producing IAsyncEnumerable<int> and IAsyncEnumerator<int> implementations. Each yield return advances a generated state machine; MoveNextAsync resumes the appropriate state and returns whether another value exists. section 14.2
Apply the interface with these practical patterns:
- Pass cancellation with collection.WithCancellation(token), and mark an async iterator parameter with [EnumeratorCancellation]. The iterator must observe the token; passing it alone does not stop work. section 14.3 section 14.8
- Use ToBlockingEnumerable only when non-asynchronous code has no alternative; it waits during each move and can lose asynchronous benefits or cause deadlocks. section 14.4
- Add asynchronous LINQ operators through the System.Linq.Async NuGet library; the stated .NET 9 baseline does not provide built-in LINQ support for IAsyncEnumerable<T>. section 14.5
Build a consuming queue
A Channel<T>-backed async iterator can expose queue consumption through await foreach. Read with ReadAsync, catch ChannelClosedException, and use yield break when the channel completes; otherwise yield each received item. This produces a BlockingCollection<T>-like consuming API without blocking readers and supports asynchronous workers. section 14.7
Table 14.2 IEnumerator vs. IAsyncEnumerator (view table figure)
| IEnumerator<T> |
IAsyncEnumerator<T> |
|---|---|
T Current {get;}
|
T Current {get;}
|
bool MoveNext() |
ValueTask<bool> MoveNextAsync() |
void Dispose() |
ValueTask DisposeAsync() |
Compare the synchronous and asynchronous enumerator contracts, including Current, MoveNext versus MoveNextAsync, and Dispose versus DisposeAsync. section 14.1

