How to break out of an IAsyncEnumerable when iterating?

Viewed 3314

C# 8 adds support for asynchronuous iterator blocks, so you can await things and return an IAsyncEnumarator instead of an IEnumerable:

public async IAsyncEnumerable<int> EnumerateAsync() {
    for (int i = 0; i < 10; i++) {
        yield return i;
        await Task.Delay(1000);
    }
}

With a non-blocking consuming code that looks like this:

await foreach (var item in EnumerateAsync()) {
    Console.WriteLine(item);
}

This will result in my code running for about 10 seconds. However, sometimes I want to break out of the await foreach before all elements are consumed. With an breakhowever, we would need to wait until the current awaited Task.Delay has finished. How can we break immediately out of that loop without waiting for any dangling async tasks?

1 Answers

The use of a CancellationToken is the solution since that is the only thing that can cancel the Task.Delay in your code. The way we get it inside your IAsyncEnumerable is to pass it as a parameter when creating it, so let's do that:

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

With the consuming side of:

// In this example the cancellation token will be caneled after 2.5 seconds
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2.5));
await foreach (var item in EnumerateAsync(cts.Token)) {
    Console.WriteLine(item);
}

Sure, this will cancel the enumeration after 3 elements were returned, but will end in an TaskCanceledException thrown out of Task.Delay. To gracefully exit the await foreach we have to catch it and break on the producing side:

public async IAsyncEnumerable<int> EnumerateAsync(CancellationToken cancellationToken = default) {
    for (int i = 0; i < 10; i++) {
        yield return i;
        try {
            await Task.Delay(1000, cancellationToken);
        } catch (TaskCanceledException) {
            yield break;
        }
    }
}

Note

As of now this is still in preview and is subject to possible change. If you are interested in this topic you can watch a discussion of the C# language team about CancellationToken in an IAsyncEnumeration.

Related