This project is read-only.
3
Vote

Add support for async enumerables

description

The async/await feature from C# 5 is awesome, but unfortunately there is no easy way to do the equivalent of an "async foreach", because IEnumerable<T> doesn't have an async equivalent. This means that we can't easily return a lazy sequence that is generated asynchronously; if an async method returns a collection, it must return the whole materialized collection.

I propose adding new IAsyncEnumerable<T> and IAsyncEnumerator<T> interfaces to the framework, and adding support for them in the C# and VB compilers.

The interfaces could look like this:
public interface IAsyncEnumerable<out T>
{
    IAsyncEnumerator<T> GetEnumerator();
}

public interface IAsyncEnumerator<out T> : IDisposable
{
    Task<bool> MoveNextAsync();
    T Current { get; }
    void Reset();
}
Now, assuming we have a method like this:
IAsyncEnumerable<Foo> GetFoosAsync()

We could write the following code:
async foreach (var foo in GetFoosAsync())
{
    // do something with foo
}
(exact syntax to be defined)

And the compiler would turn it into something like this:
using (var en = GetFoosAsync().GetEnumerator())
{
    while (await en.MoveNextAsync())
    {
        var foo = en.Current;
        // do something with foo
    }
}
Ideally, there should also be support for async iterator blocks:
IAsyncEnumerable<Foo> GetFoosAsync()
{
    using (var reader = new StreamReader("foos.txt"))
    {
        string line;
        while ((line = await reader.ReadLineAsync()) != null)
        {
            yield return new Foo(line);
        }
    }
}
As an added benefit, it would make it quite easy to create async equivalents of the common Linq operators.

comments

Halo_Four wrote Sep 8, 2014 at 9:14 PM

The existing System.IObservable<T> and System.IObserver<T> interfaces already largely handle the use-case of asynchronous sequences excepting that they function as a push model rather than a sequential pull model. The BCL does nothing with these interfaces but the Reactive Extensions project does provide some excellent support and while there is no direct language support the boilerplate is pretty minimal.
// Create an asynchronous sequence
public IObservable<int> GetNumbersAsync(int start, int count, int delay)
{
    return Observable.Create<int>(async observer =>
    {
        for (int i = 0; i < count; i++)
        {
            await Task.Delay(delay);
            // yield the next value
            observer.OnNext(start + i);
        }
    });
}
// consume an asynchronous sequence
public async Task<int> GetTotalAsync(IObservable<int> numbers)
{
    int total = 0;
    await numbers.ForEachAsync(number =>
    {
        total += number;
    });
    return total;
}
The Reactive Extensions already include extension methods covering pretty much all of the LINQ operations which allow the sequences to be composed just like IEnumerable<T>.
public async Task<int> GetTotalEvensAsync(IObservable<int> numbers)
{
    var query = from number in numbers
                where number % 2 == 0
                select number;

    int total = await query.Sum();
    return total;
}
I can (and do) argue frequently that the BCL and Rx should have more love for each other, especially since Rx is a Microsoft project. I would also love to see language support such as async iterators and async enumerations just as you've described.

tom103 wrote Sep 9, 2014 at 12:10 AM

Yes, I know about Rx and IObservable<T>, but it doesn't seem as natural to me... I should probably give it a try anyway.