Skip to content

[API Proposal]: Add an IAsyncEnumerable<T>.ToEnumerable extension method. #60106

@davidfowl

Description

@davidfowl

EDIT: See #60106 (comment) for an API proposal.

Background and motivation

Attempting to adapt an IAsyncEnumerable<T> to an IEnumerable<T>, Blocking collection seems to be the ideal type. It can be used to generate an enumerable that waits until the IAsyncEnumerable<T> produces items:

public static IEnumerable<T> ToEnumerable<T>(this IAsyncEnumerable<T> asyncEnumerable)
{
    var list = new BlockingCollection<T>();

    async Task AsyncIterate()
    {
        await foreach (var item in asyncEnumerable)
        {
            list.Add(item);
        }

        list.CompleteAdding();
    }

    _ = AsyncIterate();

    return list.GetConsumingEnumerable();
}

This works well but there's no way to propagate exceptions to the consuming enumerable that may happen when enumerating the IAsyncEnumerable<T>. I'd like to write something like this:

public static IEnumerable<T> ToEnumerable<T>(this IAsyncEnumerable<T> asyncEnumerable)
{
    var list = new BlockingCollection<T>();

    async Task AsyncIterate()
    {
        try
        {
            await foreach (var item in asyncEnumerable)
            {
                list.Add(item);
            }
            list.CompleteAdding();
        }
        catch(Exception ex)
        {
            list.CompleteAdding(ex);
        }
    }

    _ = AsyncIterate();

    return list.GetConsumingEnumerable();
}

CompleteAdding with an exception would propagate it to the caller and it would throw on synchronous enumeration. The only way to throw an exception today AFAIK is to use a cancellation token but that won't propagate the original exception.

API Proposal

namespace System.Collections.Concurrent
{
    public class BlockingCollection<T> : IEnumerable<T>
    {
        public void CompleteAdding(Exception error);
    }
}

API Usage

public static IEnumerable<T> ToEnumerable<T>(this IAsyncEnumerable<T> asyncEnumerable)
{
    var list = new BlockingCollection<T>();

    async Task AsyncIterate()
    {
        try
        {
            await foreach (var item in asyncEnumerable)
            {
                list.Add(item);
            }
            list.CompleteAdding();
        }
        catch(Exception ex)
        {
            list.CompleteAdding(ex);
        }
    }

    _ = AsyncIterate();

    return list.GetConsumingEnumerable();
}

Risks

None that I'm aware of.

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedapi-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Collectionsin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions