You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[API Proposal]: Making "Process asynchronous tasks as they complete" easy by using IAsyncEnumerable #61959
namespace System.Threading.Tasks;
public class Task
{
+ public static IAsyncEnumerable<Task> WhenEach(params Task[] tasks);+ public static IAsyncEnumerable<Task> WhenEach(params ReadOnlySpan<Task> tasks); // params when possible+ public static IAsyncEnumerable<Task> WhenEach(IEnumerable<Task> tasks);+ public static IAsyncEnumerable<Task<TResult>> WhenEach(params Task<TResult>[] tasks); // params for now; move it to ReadOnlySpan overload when that syntax is possible+ public static IAsyncEnumerable<Task<TResult>> WhenEach(params ReadOnlySpan<Task<TResult>> tasks); // params when possible+ public static IAsyncEnumerable<Task<TResult>> WhenEach(IEnumerable<Task<TResult>> tasks);
}
// Using currently available APIsList<Task<int>>downloadTasks=downloadTasksQuery.ToList();while(downloadTasks.Any()){Task<int>finishedTask=awaitTask.WhenAny(downloadTasks);downloadTasks.Remove(finishedTask);Process(awaitfinishedTask);}
API Proposal
namespaceSystem.Threading.Tasks{publicclassTask:IAsyncResult,IDisposable{publicstaticIAsyncEnumerable<Task>WhenEach(paramsTask[]tasks);// Please change the name, if neededpublicstaticIAsyncEnumerable<Task>WhenEach(IEnumerable<Task>tasks);publicstaticIAsyncEnumerable<Task<TResult>>WhenEach(paramsTask<TResult>[]tasks);publicstaticIAsyncEnumerable<Task<TResult>>WhenEach(IEnumerable<Task<TResult>>tasks);}}
API Usage
// Using newly created APIsawaitforeach(varfinishedTaskinTask.WhenEach(downloadTasksQuery)){Process(awaitfinishedTask);}
This method already exists in AsyncEx, where it's called OrderByCompletion. It returns a (non-async) collection of wrapper tasks, but I suspect that's because it's older than IAsyncEnumerable<T> and using IAsyncEnumerable<T> returning the original tasks is the better approach today.
Reacted by Huo Yaoyuan, Fraser Ashyggton, Theodor Zoulias, Vijay Nirmal and Cole Tobin
You can achieve the same behavior with TaskCompletionPipe that has native support of IAsyncEnumerable<T> as well as channel-like methods WaitToReadAsync and TryRead.
+ public static IAsyncEnumerable<Task<TResult>> WhenEach<TResult>(params Task<TResult>[] tasks); // params for now; move it to ReadOnlySpan overload when that syntax is possible+ public static IAsyncEnumerable<Task<TResult>> WhenEach<TResult>(params ReadOnlySpan<Task<TResult>> tasks); // params when possible+ public static IAsyncEnumerable<Task<TResult>> WhenEach<TResult>(IEnumerable<Task<TResult>> tasks);
You'd block synchronously in MoveNext waiting for the next task to complete.
If I'm not mistaken, OrderByCompletion returns a IEnumerable<Task<T>> which you're indeed be awaiting to examine / use however you'd like. Note there's no blocking -- MoveNext returns immediately since we're enumerating an array.
It's returning the completed task, just like with WhenAny. That gives the consumer the ability to examine / use the completed Task however they like.
So what I'm saying is that IEnumerable<Task<T>> will give you exactly that, IAsyncEnumerable seems like an auxiliary helper when you want to stop on first exception.
@alrz, how would you propose for MoveNext to immediately complete such that Current could return the next Task even when no next task had yet completed? The only way to do that would be to allocate a new Task that could be returned immediately, with a different object identity than the original task, and then whenever the next task completes, marshal its results/exception/cancellation information to that proxy. At that point, you've significantly increased the cost and you've lost the ability to compare Tasks by reference.
I'm not understanding the aversion to using IAsyncEnumerable here.
Activity
svick commentedon Nov 23, 2021
This method already exists in
AsyncEx, where it's calledOrderByCompletion. It returns a (non-async) collection of wrapper tasks, but I suspect that's because it's older thanIAsyncEnumerable<T>and usingIAsyncEnumerable<T>returning the original tasks is the better approach today.theodorzoulias commentedon Nov 25, 2021
@Vijay-Nirmal a name that might be more suitable for the
Task.WhenAnyAsEnumerablemethod isTask.WhenEach. :-)sakno commentedon Dec 10, 2021
You can achieve the same behavior with TaskCompletionPipe that has native support of
IAsyncEnumerable<T>as well as channel-like methodsWaitToReadAsyncandTryRead.bartonjs commentedon Mar 26, 2024
Video
Looks good as proposed
dahlbyk commentedon Mar 31, 2024
Nit: missing
<TResult>:alrz commentedon Apr 1, 2024
@svick
I wonder what is the benefit here. Wouldn't IEnumerable be cheaper?
At first look I thought this is going to return the value instead of the task because we're awaiting that one on MoveNextAsync.
stephentoub commentedon Apr 1, 2024
You'd block synchronously in MoveNext waiting for the next task to complete.
It's returning the completed task, just like with WhenAny. That gives the consumer the ability to examine / use the completed Task however they like.
alrz commentedon Apr 1, 2024
@stephentoub
If I'm not mistaken,
OrderByCompletionreturns aIEnumerable<Task<T>>which you're indeed be awaiting to examine / use however you'd like. Note there's no blocking -- MoveNext returns immediately since we're enumerating an array.So what I'm saying is that
IEnumerable<Task<T>>will give you exactly that,IAsyncEnumerableseems like an auxiliary helper when you want to stop on first exception.stephentoub commentedon Apr 1, 2024
@alrz, how would you propose for
MoveNextto immediately complete such thatCurrentcould return the nextTaskeven when no next task had yet completed? The only way to do that would be to allocate a new Task that could be returned immediately, with a different object identity than the original task, and then whenever the next task completes, marshal its results/exception/cancellation information to that proxy. At that point, you've significantly increased the cost and you've lost the ability to compare Tasks by reference.I'm not understanding the aversion to using
IAsyncEnumerablehere.alrz commentedon Apr 1, 2024
Note I'm talking about the the implementation in AsyncEx
https://github.com/StephenCleary/AsyncEx/blob/0361015459938f2eb8f3c1ad1021d19ee01c93a4/src/Nito.AsyncEx.Tasks/TaskExtensions.cs#L184
stephentoub commentedon Apr 1, 2024
Which allocates a new TCS / Task for every input:
https://github.com/StephenCleary/AsyncEx/blob/0361015459938f2eb8f3c1ad1021d19ee01c93a4/src/Nito.AsyncEx.Tasks/TaskExtensions.cs#L211-L213