Let's say that I have the following sample code:
private static async Task Main(string[] args)
{
var result = Enumerable.Range(0, 3).Select(x => TestMethod(x)).ToArray();
Console.ReadKey();
}
private static int TestMethod(int param)
{
Console.WriteLine($"{param} before");
Thread.Sleep(50);
Console.WriteLine($"{param} after");
return param;
}
The TestMethod will run to completion 3 times, so I'll see 3 pairs of before and after:
0 before
0 after
1 before
1 after
2 before
2 after
Now, I need to make TestMethod asynchronous:
private static async Task<int> TestMethod(int param)
{
Console.WriteLine($"{param} before");
await Task.Delay(50);
Console.WriteLine($"{param} after");
return param;
}
How can I write a similar Select expression for this async method? If I just use an async lambda Enumerable.Range(0, 3).Select(async x => await TestMethod(x)).ToArray();, that won't work because it won't wait for completion, so before parts will be called first:
0 before
1 before
2 before
2 after
0 after
1 after
Note that I don't want to run all 3 calls in parallel - I need them to execute one by one, starting the next one only when the previous one has fully finished and returned the value.
I run into this requirement regularly, and I'm not aware of any built-in solution for addressing it as at C# 7.2. I generally just fall back to using await on each asynchronous operation within a foreach, but you could go for your extension method:
public static class EnumerableExtensions
{
public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, Task<TResult>> asyncSelector)
{
var results = new List<TResult>();
foreach (var item in source)
results.Add(await asyncSelector(item));
return results;
}
}
You would then call await on the SelectAsync:
static async Task Main(string[] args)
{
var result = (await Enumerable.Range(0, 3).SelectAsync(x => TestMethod(x))).ToArray();
Console.ReadKey();
}
The disadvantage of this approach is that the SelectAsync is eager, not lazy. C# 8 promises to introduce async streams, which will allow this to be lazy again.
In C# 8 (the next major version as of the time of writing) there will be support for IAsyncEnumrable<T> where you can write async for each loops:
await foreach (var item in GetItemsAsync())
// Do something to each item in sequence
I expect there will be a Select extension method to do projection but if not then it is not difficult to write your own. You will also be able to create IAsyncEnumrable<T> iterator blocks with yield return.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With