Question

The following snippet compiles, but I'd expect it to await the task result instead of giving me a List<Task<T>>.

var foo = bars.Select(async bar => await Baz(bar)).ToList()

As pointed out here, you need to use Task.WhenAll:

var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
await Task.WhenAll(tasks);

But a comment points out that the async and await inside the Select() is not needed:

var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();

A similar question here where someone tries to use an async method inside a Where().

So async and await inside a LINQ statement is legal syntax, but does it do nothing at all or does it have a certain use?

Was it helpful?

Solution

I recommend that you not think of this as "using async within LINQ". Keep in mind what's in-between the two: delegates. Several LINQ operators take delegates, and async can be used to create an asynchronous delegate.

So, when you have an asynchronous method BazAsync that returns a Task:

Task BazAsync(TBar bar);

then this code results in a sequence of tasks:

IEnumerable<Task> tasks = bars.Select(bar => BazAsync(bar));

Similarly, if you use async and await within the delegate, you're creating an asynchronous delegate that returns a Task:

IEnumerable<Task> tasks = bars.Select(async bar => await BazAsync(bar));

These two LINQ expressions are functionally equivalent. There are no important differences.

Just like regular LINQ expressions, the IEnumerable<Task> is lazy-evaluated. Only, with asynchronous methods like BazAsync, you usually do not want accidental double-evaluation or anything like that. So, when you project to a sequence of tasks, it's usually a good idea to immediately reify the sequence. This calls BazAsync for all the elements in the source sequence, starting all the tasks going:

Task[] tasks = bars.Select(bar => BazAsync(bar)).ToArray();

Of course, all we've done with Select is start an asynchronous operation for each element. If you want to wait for them all to complete, then use Task.WhenAll:

await Task.WhenAll(tasks);

Most other LINQ operators do not work as cleanly with asynchronous delegates. Select is pretty straightforward: you're just starting an asynchronous operation for each element.

OTHER TIPS

does it have a certain use

Sure. With async and await inside a LINQ statement you can e.g. do something like this:

var tasks = foos.Select( async foo =>
    {
        var intermediate =  await DoSomethingAsync( foo );
        return await DoSomethingElseAsync( intermediate );
    } ).ToList();
await Task.WhenAll(tasks);

Without async/await inside a LINQ statement you're not awaiting anything inside the LINQ statement, so you can't process the result, or await for something else.

Without async/await, in the LINQ statement you're only starting tasks, but not waiting for them to complete. They'll still complete eventually, but it'll happen long after the control will leave the LINQ statement, so you can only access their results after the WhenAll line will complete, but not inside the LINQ statement.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top