Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: ConcatWhenEach for IAsyncEnumerable<T> #112165

Open
WhatzGames opened this issue Feb 5, 2025 · 5 comments
Open

[API Proposal]: ConcatWhenEach for IAsyncEnumerable<T> #112165

WhatzGames opened this issue Feb 5, 2025 · 5 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Linq
Milestone

Comments

@WhatzGames
Copy link

Background and motivation

Concatenating asynchronous data is currently only returned in the order in which the AsyncEnumerables are passed into Concat.
In the case that the order of the data itself does not matter, having to wait for a previous sequence to fully complete is not necessary.

E.g. collecting data through separate paginated sources like an API and a database would require for one of the two to wait for the previous stream to be completely read. (This might not be the best example, but I hope you get the idea.)

The goal of this proposal is to take the idea of Task.WhenEach() and instead of waiting for a full completion of the first sequence, it would be possible to iterate over the results as soon as one of the two sequences returns a new entry.

API Proposal

namespace System.Linq;

public static class AsyncEnumerable
{
    public static IAsyncEnumerable<T> ConcatWhenEach(this IAsyncEnumerable<T> source, IAsyncEnumerable<T> other);
    //This one is optional. As Task.WhenEach would provide an IAsyncEnumerable<T> already
    public static IAsyncEnumerable<T> ConcatWhenEach(this IAsyncEnumerable<T> source, IEnumerable<Task<T>> other);
}

API Usage

// return entries with ~3 ms delay per entry
IAsyncEnumerable<Data> source = GetDataFromAPI();
// return entries with ~1ms delay per entry 
IAsyncEnumerable<Data> other = GetDataFromDatabase();

// returns other[0], other[1], source[0], other[2], other[3], other[4], source[1], other[5] etc... 
source.ConcatWhenEach(second)

Alternative Designs

Currently I can't think of any other approach, but I'm open to any other possible suggestions and discussions.

Risks

None that come to mind.

@WhatzGames WhatzGames added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Feb 5, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Feb 5, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-linq
See info in area-owners.md if you want to be subscribed.

@eiriktsarpalis
Copy link
Member

The term "concatenation" in my mind has a strong association with chaining each of the input enumerables in order. This doesn't do that so I would suggest either adopting the original "WhenEach" terminology:

public static IAsyncEnumerable<T> WhenEach(params ReadOnlySpan<IAsyncEnumerable<T>> sources);

or perhaps

public static IAsyncEnumerable<T> Interleave(params ReadOnlySpan<IAsyncEnumerable<T>> sources);

@stephentoub opinions?

@stephentoub
Copy link
Member

FWIW, dotnet/reactive calls this particular operation Merge.

I'd like for us to examine holistically the set of APIs reactive exposes for IObservable in the System.Reactive package and for IAsyncEnumerable in the System.Interactive.Async package, and come up with a list of the additional methods we think make sense to add to AsyncEnumerable all up, rather than doing them one at a time. Where the names are reasonable, it'd also be good to use the naming already employed there (that said, some of the naming is not desirable, like "Amb").

@eiriktsarpalis eiriktsarpalis removed the untriaged New issue has not been triaged by the area owner label Feb 5, 2025
@eiriktsarpalis eiriktsarpalis added this to the Future milestone Feb 5, 2025
@julealgon
Copy link

The term "concatenation" in my mind has a strong association with chaining each of the input enumerables in order. This doesn't do that

100% agreed.

FWIW, dotnet/reactive calls this particular operation Merge.

I was going to mention either Merge or Combine would be more intuitive. Given Merge already exists, that automatically wins to me.

The term Interleave also has strong semantic behind it, usually meaning it alternates consistently. Seeing variable results on an "interleave" call would be misleading IMHO.

@toupswork
Copy link

FWIW, dotnet/reactive calls this particular operation Merge.

I'd like for us to examine holistically the set of APIs reactive exposes for IObservable in the System.Reactive package and for IAsyncEnumerable in the System.Interactive.Async package, and come up with a list of the additional methods we think make sense to add to AsyncEnumerable all up, rather than doing them one at a time. Where the names are reasonable, it'd also be good to use the naming already employed there (that said, some of the naming is not desirable, like "Amb").

Yes! What @stephentoub said! I accomplished the same thing using MergeEx I believe. But it is very prudent on Stephen's part to wait before just bringing it in as-is. If you take a look at the comments for merge and I remember there are two of them (I believe one is an extension method in the other is not), the author wrote in the comments something of a regret that he or she wished they had designed it different but now they are locked in. Read it and you'll see what I am talking about. Plus, looking out how they implemented it, it looks like there are significant opportunities to improve the performance of it, and I would much rather have the Merge method written by Stephen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Linq
Projects
None yet
Development

No branches or pull requests

5 participants