I have been looking for the best approach to creating an extension method that would give me the ability to select in a linq query using async/await with a max degree of parallelism. I'm aware that this can be achieved with the use of different libraries but wanted to create one without. Using postings I found online I made a few changes to convert a ForEachAsync to the following:
public static async Task<IEnumerable<TOut>> SelectAsync<TIn, TOut>(this IEnumerable<TIn> source,
Func<TIn, Task<TOut>> body,
int maxDegreeOfParallelism)
{
var bag = new ConcurrentBag<TOut>();
var selectAsync = Partitioner.Create(source)
.GetPartitions(maxDegreeOfParallelism)
.Select(async partition =>
{
using (partition)
{
while (partition.MoveNext())
{
var item = await body(partition.Current);
bag.Add(item);
}
}
});
await Task
.WhenAll(selectAsync);
return bag;
}
Here is the unit test:
[TestMethod]
public async Task SelectAsync_EnsureMaxConcurrency_SuccessfulOnChecksAsync()
{
// Arrange
const int itemsMax = 10;
const int dop = 5;
const int delayInMs = 1000;
var itemsToProcess = Enumerable
.Range(1, itemsMax);
// Act
var started = DateTime.Now;
var result = await itemsToProcess
.SelectAsync(async item =>
{
await Task.Delay(1000);
return item;
}, dop);
var elapsed = (DateTime.Now - started)
.TotalMilliseconds;
const double expected = (double)itemsMax / dop * delayInMs;
// Assert
Debug.WriteLine($"Elapsed:{elapsed} Expected:{expected}");
Assert.AreEqual(itemsMax, result.Count());
Assert.IsTrue(elapsed > expected);
}