Jose Maestro
Jose Maestro

Reputation: 29

await not awaiting when used in a LINQ expression inside a List<>.ForEach method call

I have an async function where I must make an async call for each of the elements of a list. To do this, I have written this piece of code:

List<string> batchItems;

batchItems.ForEach(async t => await SubmitBatchItemAsync(input, t));

However, this is not working: the SubmitBatchItemAsync is invoked, but it is not awaited.

I had to change this code to this to make it work:

List<string> batchItems;
foreach (var batchItem in batchItems)
    {
        await SubmitBatchItemAsync(input, batchItem);
    }

This also works, but it is not exactly the same, as Task.Wait() does not work exactly as await does:

List<string> batchItems;

batchItems.ForEach(t => SubmitBatchItemAsync(input, t).Wait(CancellationToken.None));

Does anyone know why the first option is not working, as LINQ expressions support await? ( https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions#async-lambdas )

Upvotes: 2

Views: 515

Answers (2)

Theodor Zoulias
Theodor Zoulias

Reputation: 43474

You may find useful this extension method for Lists:

public static async Task ForEachAsync<T>(this List<T> source, Func<T, Task> function)
{
    foreach (var item in source)
    {
        await function(item);
    }
}

Usage example:

await batchItems.ForEachAsync(async t => await SubmitBatchItemAsync(input, t));

TBH this is not as useful as it seems, because when dealing with asynchronous operations you usually want to launch them with some level of concurrency, not the one after the completion of the other.

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 456437

ForEach is not async-aware. It only takes an Action to execute on each item of the enumerable.

So this means your lambda expression async t => await SubmitBatchItemAsync(input, t) is not being converted into a Func<T, Task> like it would normally be; it's being converted into an Action<T>, forcing it to be async void.

And one of the pitfalls of async void methods is that calling code can't easily determine when they complete.

TL;DR: There are some APIs that are not async-aware, and should just be avoided when working with asynchronous code. ForEach is one of these APIs.

Upvotes: 5

Related Questions