By using code from this article, you can implement the following:
public static Task<Task<T>>[] Interleaved<T>(IEnumerable<Task<T>> tasks)
{
var inputTasks = tasks.ToList();
var buckets = new TaskCompletionSource<Task<T>>[inputTasks.Count];
var results = new Task<Task<T>>[buckets.Length];
for (int i = 0; i < buckets.Length; i++)
{
buckets[i] = new TaskCompletionSource<Task<T>>();
results[i] = buckets[i].Task;
}
int nextTaskIndex = -1;
Action<Task<T>> continuation = completed =>
{
var bucket = buckets[Interlocked.Increment(ref nextTaskIndex)];
bucket.TrySetResult(completed);
};
foreach (var inputTask in inputTasks)
inputTask.ContinueWith(continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
return results;
}
Then change your WhenEach
to call the Interleaved
code
public static async IAsyncEnumerable<TResult> WhenEach<TResult>(Task<TResult>[] tasks)
{
foreach (var bucket in Interleaved(tasks))
{
var t = await bucket;
yield return await t;
}
}
Then you can call your WhenEach
as per usual
await foreach (int result in WhenEach(tasks))
{
Console.WriteLine($"Processed: {result}");
}
I did some rudimentary benchmarking with 10k tasks and performed 5 times better in terms of speed.