A plain iterator cannot await. If each value needs a network call or a database round trip, the
old options were both bad: block a thread, or give up laziness and return a whole Task<List<T>>.
C# 8 added the third option.
C#
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async IAsyncEnumerable<int> Ticks(int count)
{
for (int i = 1; i <= count; i++)
{
await Task.Delay(10);
yield return i;
}
}
static async Task Main()
{
await foreach (var tick in Ticks(3))
{
Console.WriteLine(tick);
}
}
}
Output
1 2 3
Three pieces, and they always travel together:
asyncon the method, with return typeIAsyncEnumerable<T>yield returnin the body, alongsideawaitawait foreachat the consumer
The protocol underneath is the same shape as before, with tasks in it: GetAsyncEnumerator(),
ValueTask<bool> MoveNextAsync(), Current, and DisposeAsync().
The distinction that matters
IAsyncEnumerable<T> is not Task<IEnumerable<T>>. The first hands you values as they arrive; the
second makes you wait for all of them. If you find yourself awaiting a whole list only to loop over
it, that is the signal to reach for this.
Try it: Task 1
Open the editor