IAsyncEnumerable

Summary: in this tutorial, you will learn how to handle async streams using the IAsyncEnumerable<T> interface.

Introduction to the IAsyncEnumerable interface

Suppose you have an async method GetMessage() that returns a message after one second:

async Task<string> GetMessage(int n)
{
    await Task.Delay(1000);
    return $"Message #{n}";
}Code language: C# (cs)

To define a method that returns a sequence of messages based on the GetMessage() method, you use the IAsyncEnumerable<T> interface:

async IAsyncEnumerable<string> GetMessages(int max)
{
    for (var i = 1; i <= max; i++)
    {
        var message = await GetMessage(i);
        yield return message;
    }
}Code language: C# (cs)

The GetMessages() method calls the GetMessage() method to generate a sequence of messages. It returns an IAsyncEnumerable<T> interface that iterates the sequences asynchronously.

The IAsyncEnumerable<T> has the GetAsyncEnumerator() method that returns an enumerator which iterates asynchronously through the collection:

IAsyncEnumerator GetAsyncEnumerator(
    CancellationToken cancellationToken = default
);Code language: C# (cs)

The GetAsyncEnumerator() method has a parameter CancellationToken that allows you to cancel the asynchronous iteration. It returns an IAsyncEnumerator<T> interface that has the Current and MoveNextAsync() methods:

MemberIEnumerable<T>IAsyncEnumerator<T>
MethodGetEnumeratorGetAsyncEnumerator
MemberIEnumerator<T>IAsyncEnumerator<T>
PropertyCurrentCurrent
MethodMoveNext()MoveNextAsync()

To consume an IAsyncEnumerator, you use the await foreach statement instead of the foreach statement:

await foreach (var message in GetMessages(5))
{
    WriteLine(message);
}Code language: C# (cs)

Output:

Message #1
Message #2
Message #3
Message #4
Message #5Code language: C# (cs)

The program displays five messages after every second.

Put it all together.

using static System.Console;


await foreach (var message in GetMessages(5))
{
    WriteLine(message);
}


async IAsyncEnumerable<string> GetMessages(int max)
{
    for (var i = 1; i <= max; i++)
    {
        var message = await GetMessage(i);
        yield return message;
    }
}

async Task<string> GetMessage(int n)
{
    await Task.Delay(1000);
    return $"Message #{n}";
}Code language: C# (cs)

Using the IAsyncEnumerable interface with CancellationToken

The following example shows how to use the CancellationToken to request the cancellation of an operation:

using System.Runtime.CompilerServices;
using static System.Console;

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));

try
{
    await foreach (var message in GetMessages(5, cts.Token))
    {
        WriteLine(message);
    }
}
catch(TaskCanceledException ex)
{
    WriteLine(ex.Message);
}

async IAsyncEnumerable<string> GetMessages(int max, [EnumeratorCancellation] CancellationToken token = default)
{
    for (var i = 1; i <= max; i++)
    {
        var message = await GetMessage(i, token);
        yield return message;
    }
}

async Task<string> GetMessage(int n, CancellationToken token=default)
{
    await Task.Delay(1000, token);
    return $"Message #{n}";
}Code language: C# (cs)

Output:

Message #1
Message #2
A task was canceled.Code language: C# (cs)

How it works.

First, create a CancellationTokenSource object and schedule a cancel operation after 3 seconds:

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));Code language: C# (cs)

Second, add the CancellationToken to both GetMessage() and GetMessages() methods. In the GetMessage() method, pass the token to the Task.Delay() method.

Practical use of the IAsyncEnumerable interface

The following program illustrates how to retrieve the content of a list of URLs and writes the length of each downloaded text to the console using the IAsyncEnumerable and HttpClient:

using static System.Console;

var urls = new List<string>()
{
    "https://datatracker.ietf.org/doc/html/rfc791",
    "https://datatracker.ietf.org/doc/html/rfc1180",
    "https://datatracker.ietf.org/doc/html/rfc2616"
};


await foreach (var result in GetTexts(urls))
{
    WriteLine(result?.Length);
}

static async IAsyncEnumerable<string?> GetTexts(List<string> urls)
{

    foreach (var url in urls)
    {
        var text = await Download(url);
        yield return text;
    }
}

static async Task<string?> Download(string url)
{
    try
    {
        using var client = new HttpClient();
        
        var response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        var text = await response.Content.ReadAsStringAsync();
        return text;
    }
    catch (HttpRequestException ex)
    {
        WriteLine(ex.Message);
        return null;
    }
}Code language: C# (cs)

Output:

126891
102930
580146Code language: C# (cs)

Summary

  • Use IAsyncEnumerable<T> and await foreach statement to iterate over a sequence of items asynchronously.
Was this tutorial helpful ?