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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…