Shachaf.Gortler Shachaf.Gortler - 1 month ago 24
C# Question

difference between async task and Task.WhenAll

what is the difference when using await on multiple await task vs waiting on all the tasks to finish, my under standing is the scenario 2 is better in terms of performance because both asyncTask1 and asyncTask2 are preformed in parallel.

scenario 1 :

async Task Task()
{
await asyncTask1();
await asyncTask2();
}


scenario 2 :

async Task Task()
{
t1 = asyncTask1();
t2 = asyncTask2();
await Task.WhenAll(createWorkflowtask, getTaskWorkflowTask);
}

Answer

You wrote: "... because both asyncTask1 and asyncTask2 are preformed in parallel."

No they are not!

Schneider's comment on this statement is correct. See the addition at the end.

An article that helped me a lot to understand how async-await works was this interview with Eric-Lippert who compared async/await with a cook making dinner. (Somewhere half-way, search for async-await).

Eric Lippert describes that if a cook starts doing something and finds after a while he has nothing else to do but wait for a process to finish, this cook looks around to see if he can do something else instead of waiting.

When using async/await, there is still one thread involved. This thread can do only one thing at a time. While the thread is busy doing Task1, it can't execute Task2. Only if it finds an await in Task1, it will start executing statements from Task2. In your scenario 2 the tasks are not executed in parallel.

However there is a difference between the scenarios. In scenario 1 the first statement of task2 will not be executed before task1 completely finishes. Scenario 2 will start executing the first statements of task2 as soon as task1 encounters an await.

If you really want task2 to do something while task1 is also doing something, you'll have to start doing task2 in a separate thread. The easy method to do this in your scenario would be:

var task1 = Task.Run( () => asyncTask1())
// this statement is executed while task1 begins executing on a different thread.
// hence this thread is free to do other things, like performing statements
// from task2:
var task2 = asyncTask();
// the following statement will only be executed if task2 encounters an await
DoSomethingElse();
// when we need results from both task1 and task2:
await Task.WhenAll(new Task[] {task1, task2});

So usually, it is best only to await for a task to finish if you need the result of this task. As long as you can do other things, do these other things, they will be executed as soon as the other task starts awaiting until you start awaiting, in which case your caller will start doing things until his await etc.

The advantage of this method above doing things in parallel are manyfold:

  • Everything is performed by one thread: no need of mutexes, no chance of deadlock, starvation, etc
  • Your code looks quite sequential. Compare this with code that uses Task.ContinueWith and similar statements
  • There is no overhead of starting a separate thread / running a thread from the thread pool

Addition: Schneider's comment below about several threads is correct.

Some testing showed me that the thread ID of the current thread in the awaitable tasks is different than the thread ID of the calling thread.

For newbees to async-await it is important to understand that although various threads are involved, async-await does NOT mean that the tasks are performed in parallel. If you want parallelism you specifically have to say that the task must be run in parallel.

It seems that the cook in Eric Lippert's analogy is in fact a team of cooks, who constantly look around to see if they can help some of the other cooks instead of waiting for their tasks to finish. And indeed if cook A starts encounters an await and starts doing something else, cook B might finish the task.

Comments