Why Should I Prefer Single 'Await Task.Whenall' Over Multiple Awaits

Why should I prefer single 'await Task.WhenAll' over multiple awaits?

Yes, use WhenAll because it propagates all errors at once. With the multiple awaits, you lose errors if one of the earlier awaits throws.

Another important difference is that WhenAll will wait for all tasks to complete even in the presence of failures (faulted or canceled tasks). Awaiting manually in sequence would cause unexpected concurrency because the part of your program that wants to wait will actually continue early.

I think it also makes reading the code easier because the semantics that you want are directly documented in code.

WhenAll vs New object with multiple awaits

Yes, there's enormous difference between the two.

  1. In the first case, all 3 tasks will proceed in parallel and independently assuming there's no resource contention among them.

    However, in the second case they'll only proceed one by one.

  2. The remarks section for WhenAll() is also significant.

  3. Depending upon the synchronization context in effect, if any at all, the point where you experience a potential deadlock is also different for both.

  4. The point where an exception thrown by the task is visible differs.

Performance of multiple awaits compared to Task.WhenAll


Why is the method using Task.WhenAll so much faster

It is faster because you are not awaiting GetPostAsync. So actually every time you await client.GetAsync($"https://jsonplaceholder.typicode.com/posts/{id}"); the control will be returned to the caller which then can make another HTTP request. If you consider that HTTP request is much longer than creating the new client you effectively have the parallelism by running multiple HTTP requests in parallel. The WhenAll will just create a suspension point and wait for all tasks to finish.

With the multiple await approach, you make HTTP requests sequentially one by one by await GetPostAsync(postId) from foreach loop. You start the task but at the same time, you make a suspension point and wait for it to finish.

are there any negative effects (i.e exception handling, etc.) when
choosing this approach over the other?

There are no negative effects, using await/async pattern handling exception become just, as usual, using try-catch block. WhenAll will aggregate all exception from each task which is in Faulted state.

multiple awaits vs Task.WaitAll - equivalent?

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.

How to simplify multiple awaits with a single 'await Task.WhenAll'?

If you want both task to execute simultaneously, then don't await the methods. Instead pass their tasks into a variables and call them in Task.WhenAll

public async Task UpdateData() {
var month = (cbMonths.SelectedItem as MonthView).ID;
var year = (cbYears.SelectedItem as YearView).ID;
var deviceTypeID = (int)DeviceType;
var task1 = GetCalendar(month, year, deviceTypeID);
var task2 = GetWorkTypes();

await Task.WhenAll(task1, task2);

var calendar = task1.Result;
var workTypes = task2.Result;
}

Also note that you should avoid async void methods.

Why Multiple await take same time like Task.WhenAll()

Task.Delay internally uses a timer to notify the program when it should continue. The timer starts as soon as you call Task.Delay. In both cases you start the tasks one after another as you store the tasks in variables, only to await them later. While awaiting any of them, the timers are still going in the background and because they started more or less at the same time, they finish when the one with the longest delay finishes

var firstTask = SleepForTime(10000);
var secondTask = SleepForTime(7000);
var thirdTask = SleepForTime(5000);

// All of the tasks are already started
Console.WriteLine("Start");
await firstTask; //this finishes after ~10s
Console.WriteLine("First finished");
await secondTask; //this finishes immediately
Console.WriteLine("Second finished");
await thirdTask; //this also finishes immediately
Console.WriteLine("Third finished");

All of the First/Second/Third finished messages show up almost at the same time, after 10s. You can see a change after some modification:

var firstTask = SleepForTime(5000);
var secondTask = SleepForTime(7000);
var thirdTask = SleepForTime(10000);

// All of the tasks are already started
Console.WriteLine("Start");
await firstTask; //this finishes after ~5s
Console.WriteLine("First finished");
await secondTask; //this finishes after 2 more seconds
Console.WriteLine("Second finished");
await thirdTask; //this finishes after 3 more seconds
Console.WriteLine("Third finished");

Now First finished shows up after 5s, Second finished after another 2s and Third finished after another 3s.

To get the desired result you'd have to call the functions sequentially and await each one right there, like this:

Console.WriteLine("Start");
await SleepForTime(10000); //this finishes after 10s
Console.WriteLine("First finished");
await SleepForTime(7000); //this finishes after 7s
Console.WriteLine("Second finished");
await SleepForTime(5000); //this finishes after 5s
Console.WriteLine("Third finished");

The SleepForTime function is a perfect candidate for some style improvements - use the suffix Async to indicate that it should be used in asynchronous code and you can return the task returned from Task.Delay itself making the code a little simpler (for you and the compiler)

public static Task SleepForTimeAsync(int seconds)
{
return Task.Delay(seconds);
}

To use Task.WhenAll, or not to use Task.WhenAll


My question really is, is there a technical reason why you should or not use Task.WhenAll()?

The behavior is just slightly different in the case of exceptions when both calls fail. If they're awaited one at a time, the second failure will never be observed; the exception from the first failure is propagated immediately. If using Task.WhenAll, both failures are observed; one exception is propagated after both tasks fail.

Is it just a preference?

It's mostly just preference. I tend to prefer WhenAll because the code is more explicit, but I don't have a problem with awaiting one at a time.

When do multiple awaits make sense?

The difference is:

First example:

  • You queue Do1 on a threadpool thread and asynchronously wait for it to complete, then do the exact same with Do2. These may run on different threads.
  • You queue Do1 and Do2 to execute synchronously one after the other on the same thread pool thread.

Second example:

  • Queue Do1 on the threadpool and asynchronously wait for it to complete, then invoke Do3 synchronously.
  • This is exactly the same as the second part of the first example.

Note that when you await, you asynchronously wait for the operation to complete, hence unless the method finishes it won't execute the next line of code.

I'm assuming you're asking yourself if one is preferable to the other, and as in most cases, it depends. If you're running inside a console app, and you're going to asynchronously wait for Do1 to complete anyway, then pass both methods to the same Task.Run invocation. If you're planning on doing this in a place where synchronization matters, such as a GUI application, then any operation which needs to interact with UI controls should be invoked on the UI thread.

Another option which is more common is when you have two operations which are independent of each other and you want to start them together and wait for both to complete. This is where you'd use Task.WhenAll:

var firstDo = Task.Run(() => Do1());
var secondDo = Task.Run(() => Do2());
await Task.WhenAll(firstDo, secondDo);

Side note:

Do not use async void in asynchronous methods with no return value, that is what async Task is for. The former is only meant to allow compatibility with event handlers, where I'm assuming this isn't the case.

TaskTupleAwaiter vs Task.WhenAll?

Based on the source code await (t1(), t2()) should be the same as:

var t1task = t1();
var t2task = t2();
await Task.WhenAll(t1task, t2task);
var r1 = t1task.Result;
var r2 = t2task.Result;

Which should be preferable compared to the original code (now commented out) in the question.

The project hasn't had any update for a while.

The source code was updated only 5 month ago, project targets latest released (at the moment of writing) version of .NET - .NET 6. I would say that for smaller projects which does not have a lot of features such maintainability schedule should be just fine, so personally I see no major reason to switch.



Related Topics



Leave a reply



Submit