async programming
C#
generators
enumerable
await

Can I await an enumerable I create with a generator?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, you cannot await a normal IEnumerable produced by an iterator method. await works on awaitable values such as Task and ValueTask, while a synchronous enumerable is just a pull-based sequence of values.

Understand the Type You Actually Have

A generator-style iterator in C# usually means yield return, which produces IEnumerable<T> or IAsyncEnumerable<T> depending on how it is written.

A normal synchronous iterator:

csharp
1using System.Collections.Generic;
2
3IEnumerable<int> Numbers()
4{
5    yield return 1;
6    yield return 2;
7    yield return 3;
8}

You consume that with foreach, not await:

csharp
1foreach (var number in Numbers())
2{
3    Console.WriteLine(number);
4}

await Numbers() does not make sense because IEnumerable<int> is not an awaitable operation.

Use Task.WhenAll If the Enumerable Contains Tasks

Sometimes the enumerable itself is synchronous, but it contains asynchronous operations:

csharp
1using System.Collections.Generic;
2using System.Threading.Tasks;
3
4IEnumerable<Task<int>> Work()
5{
6    yield return Task.FromResult(1);
7    yield return Task.FromResult(2);
8    yield return Task.FromResult(3);
9}

In that case, you do not await the enumerable. You await the tasks it contains:

csharp
var results = await Task.WhenAll(Work());

That is a very different pattern from awaiting the sequence object itself.

This distinction is easy to miss because both patterns involve "many asynchronous things". But the shape of the types matters:

  • 'IEnumerable<Task<T>> means a normal sequence of asynchronous operations'
  • 'IAsyncEnumerable<T> means an asynchronously produced sequence of values'

Use IAsyncEnumerable<T> for Asynchronous Iteration

If the generator itself is asynchronous over time, use async iterators and consume them with await foreach.

csharp
1using System.Collections.Generic;
2using System.Threading.Tasks;
3
4async IAsyncEnumerable<int> NumbersAsync()
5{
6    await Task.Delay(100);
7    yield return 1;
8
9    await Task.Delay(100);
10    yield return 2;
11}

Consume it like this:

csharp
1await foreach (var number in NumbersAsync())
2{
3    Console.WriteLine(number);
4}

This is the C# feature that most closely matches "an awaitable sequence from a generator".

It is also the right tool when values arrive over time and you want to process them one by one instead of waiting for every result up front.

Materialize an Async Sequence When You Need a Final Result

Sometimes you do want a single awaited result, but the correct shape is "consume the async sequence, then return a collection". In that case, iterate with await foreach and build the result yourself:

csharp
1using System.Collections.Generic;
2using System.Threading.Tasks;
3
4async Task<List<int>> CollectAsync()
5{
6    var values = new List<int>();
7
8    await foreach (var number in NumbersAsync())
9    {
10        values.Add(number);
11    }
12
13    return values;
14}

This pattern makes the control flow explicit. You are not awaiting the sequence object itself. You are awaiting each asynchronous move to the next item until the sequence is exhausted, then returning a regular List<int> wrapped in a Task<List<int>>.

Common Pitfalls

The biggest mistake is trying to use await on IEnumerable<T> directly. A sequence is not the same thing as an asynchronous operation.

Another common issue is confusing "enumerable of tasks" with "async enumerable". IEnumerable<Task<T>> means the sequence is synchronous but each element is asynchronous. IAsyncEnumerable<T> means the sequence itself is asynchronous to iterate.

People also forget that await foreach requires IAsyncEnumerable<T>, not just any collection with delayed work inside it.

Finally, if you only need all asynchronous results at once, Task.WhenAll is often simpler than creating an async iterator.

Summary

  • You cannot await a normal IEnumerable<T>.
  • If the enumerable yields tasks, await those tasks with something like Task.WhenAll.
  • If the sequence itself is asynchronous, use IAsyncEnumerable<T> and await foreach.
  • Distinguish clearly between synchronous sequences and asynchronous iteration.
  • Choose the pattern based on whether the asynchrony belongs to the items or to the sequence itself.

Course illustration
Course illustration

All Rights Reserved.