jahav
jahav

Reputation: 720

Control flow for multiple tasks during await

I have trouble understanding control flow during await when I have two tasks and I await one. The Control Flow in Async Programs (C# and Visual Basic) uses only one await so it didn't help.

I have following code:

public async Task Loop(string name, int count)
{
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine("{0}: {1} Before", name, i);
        await Task.Delay(10);
        Console.WriteLine("{0}: {1} After", name, i);
    }
    Console.WriteLine("{0} COMPLETE", name);
}

[TestMethod]
public async Task TwoLoopsWithSeparateAwait()
{
    var loopOne = Loop("ONE", 3);
    Console.WriteLine("After ONE Creation");
    var loopTwo = Loop("TWO", 3);
    Console.WriteLine("After TWO Creation, before ONE await");
    await loopOne;
    Console.WriteLine("After ONE await");
    await loopTwo;
    Console.WriteLine("After TWO await");
}

with following result:

According to the await (C# Reference):

An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.

I understand what happens until await loopOne, but I thought that only loopOne would be executed (probably synchronously), it looks like await behaves more like a yield that keeps list of possible tasks that may continue (I suppose that qualifies for "When the task completes, it invokes its continuation").

What is the logic behind control flow during await for multiple tasks?

Upvotes: 3

Views: 1296

Answers (1)

i3arnon
i3arnon

Reputation: 116636

An async method is executed synchronously until it reaches an await. It then creates a task that represents the async operation you're awaiting together with the rest of the code as a continuation to be executed when the operation completes. That means that in an async method when the first await is reached the control is actually returned to the caller with a hot task. When an await is reached inside that continuation it again registers a continuation and so forth.

So in your case, the execution order should be:

  • The synchronous part of the first Loop.
  • The synchronous part of the second Loop.
  • A continuation of the first Loop
  • A continuation of the second Loop.
  • ...
  • The caller's continuation after the first Loop.
  • The caller's continuation after the second Loop.

The continuations in the middle could easily be in a different order depending on how long the asynchronous operations actually took.

You could imagine 3 different flows here. The first one executes the synchronous parts of both Loop calls, start 2 concurrent flows of async operations and continuations and then registers a continuation to the first call that will register a continuation to the second.

Upvotes: 6

Related Questions