If possible I want to create an async-enumerator for tasks launched in parallel. So first to complete is first element of the enumeration, second to finish is second element of
Here is a version that also allows to specify the maximum degree of parallelism. The idea is that the tasks are enumerated with a lag. For example for degreeOfParallelism: 4
the first 4 tasks are enumerated immediately, causing them to be created, and then the first one of these is awaited. Next the 5th task is enumerated and the 2nd is awaited, and so on.
To keep things tidy, the Lag
method is embedded inside the ParallelEnumerateAsync
method as a static local function (new feature of C# 8).
public static async IAsyncEnumerable ParallelEnumerateAsync(
this IEnumerable> tasks, int degreeOfParallelism)
{
if (degreeOfParallelism < 1)
throw new ArgumentOutOfRangeException(nameof(degreeOfParallelism));
if (tasks is ICollection>) throw new ArgumentException(
"The enumerable should not be materialized.", nameof(tasks));
foreach (var task in Lag(tasks, degreeOfParallelism - 1))
{
yield return await task.ConfigureAwait(false);
}
static IEnumerable Lag(IEnumerable source, int count)
{
var queue = new Queue();
using (var enumerator = source.GetEnumerator())
{
int index = 0;
while (enumerator.MoveNext())
{
queue.Enqueue(enumerator.Current);
index++;
if (index > count) yield return queue.Dequeue();
}
}
while (queue.Count > 0) yield return queue.Dequeue();
}
}
Note: this implementation is flawed regarding maintaining a consistent degree of parallelism. It depends on all tasks having similar completion durations. A single long running task will eventually drop the degree of parallelism to one, until it is completed.