StephenCleary / AsyncEx

A helper library for async/await.
MIT License
3.49k stars 358 forks source link

Feature request: OrderByCompletion retuning an IAsyncEnumerable<T> #198

Closed wbuck closed 3 years ago

wbuck commented 4 years ago

Hello Stephen, I was wondering if it would be possible to have the OrderByCompletion return an IAsyncEnumerable<T> instead of a List<Task<T>> (making it an async stream).

await foreach ( var value in tasks.OrderByCompletion( token ) ) { }

I'm unsure if this would be at all possible and I haven't really thought through the implications of this. Currently to achieve what I want I've written some ugly code using the ye olde Task.WhenAny in a loop and yield returning the result from the completed Task. I know this isn't the most efficient way of doing it. I'm just curious if something like this would be possible.

By the way, really enjoying the new book.

StephenCleary commented 4 years ago

The current OrderByCompletion does immediately return a collection of tasks that would be completed in order, so it seems to do what you want already?

An async streams OrderByCompletion would make sense if the number of source tasks were unknown. I'm not sure how useful that would be - it seems unusual to have an IAsyncEnumerable<Task<T>>, which would be the input for that kind of operator.

vitidev commented 4 years ago

would make sense if the number of source tasks were unknown.

That's exactly when it is useful. And concurrency limit. And results buffering. Of course, we can use Dataflow, but it can be easier

static async Task Main(string[] args)
{
    IEnumerable<string> urls = ...; //infinite (or not. why not?)

    await foreach (var downloadFileTask in  urls.Select(DownloadAsync).ForEachAsync(concurrency: 3 ,capacity: 20))
    {
         ...
    }
}

private static Task<string> DownloadAsync(string url)...
Arithmomaniac commented 3 years ago

It's trivial to do yourself:

public static async IAsyncEnumerable<T> ToAsCompletedAsyncEnumerable<T>(this IEnumerable<Task<T>> tasks)
{
    foreach (var task in tasks.OrderByCompletion())
    {
        yield return await task.ConfigureAwait(false);
    }
}
vitidev commented 3 years ago

@Arithmomaniac

And block a thread from the threadpool to wait for the next task in foreach? what a waste. I would like something like this

Arithmomaniac commented 3 years ago

@vitidev

Clearly I'm still rusty on how await works; isn't the point that awaiting a task is effectively thread-free?

vitidev commented 3 years ago

@Arithmomaniac

Clearly I'm still rusty on how await works;

It looks like I'm wrong. It seems I poor understand well how IAsyncEnumerable with yield return await works

StephenCleary commented 3 years ago

Closing this as OrderByCompletion handles the common case (including IEnumerable<Task> input).

For the dynamically-added case (IAsyncEnumerable<Task> input), I would prefer operators (like "approximate order by completion") become a part of System.Linq.Async rather than AsyncEx.