multiple awaits vs Task.WaitAll - equivalent?
Solution 1
The first option will not execute the two operations concurrently. It will execute the first and await its completion, and only then the second.
The second option will execute both concurrently but will wait for them synchronously (i.e. while blocking a thread).
You shouldn't use both options since the first completes slower than the second and the second blocks a thread without need.
You should wait for both operations asynchronously with Task.WhenAll
:
public async Task<IHttpActionResult> MethodB()
{
var customer = new Customer();
var getAllWidgetsTask = _widgetService.GetAllWidgets();
var getAllFoosTask = _fooService.GetAllFos();
await Task.WhenAll(getAllWidgetsTask, getAllFoosTask);
customer.Widgets = await getAllWidgetsTask;
customer.Foos = await getAllFoosTask;
return Ok(customer);
}
Note that after Task.WhenAll
completed both tasks already completed so awaiting them completes immediately.
Solution 2
Short answer: No.
Task.WaitAll
is blocking, await
returns the task as soon as it is encountered and registers the remaining part of the function and continuation.
The "bulk" waiting method you were looking for is Task.WhenAll
that actually creates a new Task
that finishes when all tasks that were handed to the function are done.
Like so: await Task.WhenAll({getAllWidgetsTask, getAllFoosTask});
That is for the blocking matter.
Also your first function does not execute both functions parallel. To get this working with await
you'd have to write something like this:
var widgetsTask = _widgetService.GetAllWidgets();
var foosTask = _fooService.GetAllWidgets();
customer.Widgets = await widgetsTask;
customer.Foos = await foosTask;
This will make the first example to act very similar to the Task.WhenAll
method.
vidalsasoon
Updated on July 05, 2022Comments
-
vidalsasoon almost 2 years
In terms of performance, will these 2 methods run
GetAllWidgets()
andGetAllFoos()
in parallel?Is there any reason to use one over the other? There seems to be a lot happening behind the scenes with the compiler so I don't find it clear.
============= MethodA: Using multiple awaits ======================
public async Task<IHttpActionResult> MethodA() { var customer = new Customer(); customer.Widgets = await _widgetService.GetAllWidgets(); customer.Foos = await _fooService.GetAllFoos(); return Ok(customer); }
=============== MethodB: Using Task.WaitAll =====================
public async Task<IHttpActionResult> MethodB() { var customer = new Customer(); var getAllWidgetsTask = _widgetService.GetAllWidgets(); var getAllFoosTask = _fooService.GetAllFos(); Task.WaitAll(new List[] {getAllWidgetsTask, getAllFoosTask}); customer.Widgets = getAllWidgetsTask.Result; customer.Foos = getAllFoosTask.Result; return Ok(customer); }
=====================================
-
vidalsasoon over 8 yearsThanks. This is what I needed. The ".Result" was bothering me and your answer avoids that.
-
i3arnon over 8 years@vidalsasoon sure.. any time.
-
kwesolowski over 8 yearsYou can also completely skip
await Task.WhenAll(getAllWidgetsTask, getAllFoosTask);
and just await tasks (just start second task before awaiting first). -
i3arnon over 8 years@kwesoly that's true, but it's a little bit more efficient to "suspend" only once.
-
kwesolowski over 8 years@i3arnon - sounds as good reason, there is really no point to switch context just to await again.
-
Stephen Holt over 5 yearsThis seems a little inelegant, as we're using "await" on the two tasks after we already know they've completed. What is wrong with just using the .Result property?
-
i3arnon over 5 years@StephenHolt what's wrong with using
await
?await
doesn't care whether the task is completed or nor (it even optimizes if it is). -
Servy over 5 years@StephenHolt There's exactly one difference between the two.
Result
wraps any exceptions in anAggregateException
, which you then need to unwrap in all of your error handling code.await
unwraps it for you. Other than that they're identical. -
Stephen Holt over 5 years@i3arnon no reason I guess... I'd just never seen await used in that context before, and I just thought async implied something that should take a while to complete. Thanks to yourself and Servy for explaining.
-
Theodor Zoulias almost 4 yearsThis a good answer, but it should be noted that the proposed solution has a "disadvantage" over the original example with
Task.WaitAll
. The disadvantage is that in case both internal tasks fail (getAllWidgetsTask
andgetAllFoosTask
), only the exception of the first task is going to be propagated. On the contrary the originalTask.WaitAll
will propagate both exceptions wrapped in anAggregateException
. -
i3arnon almost 4 years@TheodorZoulias
Task.When
returns a task. If the task will be faulted its Exception property will hold anAggregateException
with all the inner exceptions.await
only rethrows the first, but you can catch it and look at the task for the rest. -
Theodor Zoulias almost 4 yearsYeap, indeed. But the method
MethodB
is not doing any error handling internally. It just propagates a possible exception to the caller. And by usingawait
it silently discards all exceptions except from the first. I am not saying that this is hugely important. I am just pointing out to a difference between the two implementations, that is not obvious at first glance, and may have significance in some cases. -
harry over 2 years@TheodorZoulias has a very important point, but the only advantage not mentioned here to starting both tasks then awaiting separately is that it allocates (much) less. I'm sure as the number of tasks goes up, it'll even out then maybe weigh in
Task.WhenAll
's favor, but with only two tasks, awaiting them separately allocates a good deal less. See benchmark here: dotnetfiddle.net/hcd6Jb -
Theodor Zoulias over 2 years@Mahmoud Al-Qudsi my comment was about the difference between
Task.WaitAll
andawait Task.WhenAll
, not betweenawait Task.WhenAll
and awaiting each task individually. Individually awaiting each task has serious repercussions that make any benchmark results mostly irrelevant. If the first task fails, the second task will be leaked as a fire-and-forget task. This is rarely what you want. In most cases you don't want to lose track of your tasks, and have them running rampant out of control. -
harry over 2 yearsOh, I know - that’s why I said you had a good point (in my case, each task had a continuation running OnlyOnFaulted that handled the fallout).
-
Theodor Zoulias over 2 years@Mahmoud Al-Qudsi are you awaiting those
OnlyOnFaulted
continuations, or are themselves fire-and-forget?