79

In terms of performance, will these 2 methods run GetAllWidgets() and GetAllFoos() 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);
}

=====================================

i3arnon
  • 113,022
  • 33
  • 324
  • 344
vidalsasoon
  • 4,365
  • 1
  • 32
  • 40
  • 3
    In your first example the two methods will be called sequentially and in the second they'll be run in parallel, so they aren't equivalent. Also, in your second method you are blocking while executing the tasks. – Daniel Kelley Aug 20 '15 at 13:27
  • MethodA will execute `_fooService.GetAllFoos()` only when `_widgetService.GetAllWidgets()` has finished, methodB will execute it when the uncompleted Task from `_fooService.GetAllFoos()` returns. – shay__ Aug 20 '15 at 13:29
  • @DanielKelley The second does not guarantee that they'll be run in parallel. As is so often emphasized in other answers and explanations of asynchronous operations, they are not necessarily multi-threaded or parallel. Some even insist that they are not multi-threaded at all, but that is an unnecessary limitation. Depending on the implementation, they might be run in parallel, but about the strongest that can be said is that they are not guaranteed to run sequentially. – C Perkins Oct 22 '21 at 01:07

5 Answers5

139

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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 2
    Thanks. This is what I needed. The ".Result" was bothering me and your answer avoids that. – vidalsasoon Aug 20 '15 at 13:33
  • @vidalsasoon sure.. any time. – i3arnon Aug 20 '15 at 13:34
  • 13
    You can also completely skip `await Task.WhenAll(getAllWidgetsTask, getAllFoosTask);` and just await tasks (just start second task before awaiting first). – kwesolowski Aug 20 '15 at 13:43
  • 3
    @kwesoly that's true, but it's a little bit more efficient to "suspend" only once. – i3arnon Aug 20 '15 at 13:44
  • 1
    @i3arnon - sounds as good reason, there is really no point to switch context just to await again. – kwesolowski Aug 20 '15 at 14:12
  • 1
    This 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? – Stephen Holt Aug 01 '18 at 15:49
  • @StephenHolt what's wrong with using `await`? `await` doesn't care whether the task is completed or nor (it even optimizes if it is). – i3arnon Aug 01 '18 at 22:14
  • 4
    @StephenHolt There's exactly one difference between the two. `Result` wraps any exceptions in an `AggregateException`, which you then need to unwrap in all of your error handling code. `await` unwraps it for you. Other than that they're identical. – Servy Aug 01 '18 at 22:17
  • 1
    @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. – Stephen Holt Aug 02 '18 at 09:26
  • 1
    This 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` and `getAllFoosTask`), [only the exception of the first task](https://stackoverflow.com/questions/18314961/i-want-await-to-throw-aggregateexception-not-just-the-first-exception) is going to be propagated. On the contrary the original `Task.WaitAll` will propagate both exceptions wrapped in an `AggregateException`. – Theodor Zoulias Jun 14 '20 at 13:48
  • @TheodorZoulias `Task.When` returns a task. If the task will be faulted its Exception property will hold an `AggregateException` with all the inner exceptions. `await` only rethrows the first, but you can catch it and look at the task for the rest. – i3arnon Jun 15 '20 at 14:24
  • 1
    Yeap, indeed. But the method `MethodB` is not doing any error handling internally. It just propagates a possible exception to the caller. And by using `await` 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. – Theodor Zoulias Jun 15 '20 at 14:36
  • @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: https://dotnetfiddle.net/hcd6Jb – Mahmoud Al-Qudsi Nov 05 '21 at 22:33
  • @Mahmoud Al-Qudsi my comment was about the difference between `Task.WaitAll` and `await Task.WhenAll`, not between `await 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. – Theodor Zoulias Nov 05 '21 at 22:46
  • Oh, 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). – Mahmoud Al-Qudsi Nov 05 '21 at 23:09
  • @Mahmoud Al-Qudsi are you awaiting those `OnlyOnFaulted` continuations, or are themselves fire-and-forget? – Theodor Zoulias Nov 06 '21 at 07:07
24

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.

Nitram
  • 6,486
  • 2
  • 21
  • 32
1

As an addition to what @i3arnon said. You will see that when you use await you are forced to have to declare the enclosing method as async, but with waitAll you don't. That should tell you that there is more to it than what the main answer says. Here it is:

WaitAll will block until the given tasks finish, it does not pass control back to the caller while those tasks are running. Also as mentioned, the tasks are run asynchronous to themselves, not to the caller.

Await will not block the caller thread, it will however suspend the execution of the code below it, but while the task is running, control is returned back to the caller. For the fact that control is returned back to the caller (the called method is running async), you have to mark the method as async.

Hopefully the difference is clear. Cheers

pnizzle
  • 6,243
  • 4
  • 52
  • 81
0

Only your second option will run them in parallel. Your first will wait on each call in sequence.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
0

As soon as you invoke the async method it will start executing. Whether it will execute on the current thread (and thus run synchronously) or it will run async is not possible to determine.

Thus, in your first example the first method will start doing work, but then you artificially stops the flow of the code with the await. And thus the second method will not be invoked before the first is done executing.

The second example invokes both methods without stopping the flow with an await. Thus they will potentially run in parallel if the methods are asynchronous.

Kasper Holdum
  • 12,993
  • 6
  • 45
  • 74