Search code examples
c#multithreadingasynchronousasync-await

Await/async on synchronous methods, what is an "asynchronous process"? When does "control return to the caller"? Nested awaits


After reading a lot about async/await I still have a fundamental question (that I kind of assume the answer but I just want confirmation).

Let's discuss over some code:

async Task methodA() {
    //code A (without awaits or Task.Run)
    Task.Run(()=> //code A' (without awaits or Task.Run))
}

async Task methodB() {
    //code B (without awaits or Task.Run)
    await methodA();
}


void Main() {
    //code C (without awaits or Task.Run)
    methodB(); (no await)
    //code D (without awaits or Task.Run, no pun intended)
}

What I think will happen:

  1. Run code C
  2. Run code B
  3. Run code A
  4. Run code A' and code D (different threads, in parallel)

The doubt comes from what's in the documentation:

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. [...] The await operator doesn't block the thread that evaluates the async method.

The key being here what represents an asynchronous operation. Cause considering this other piece of documentation:

The await operator tells the compiler that the async method can't continue past that point until the awaited asynchronous process is complete. In the meantime, control returns to the caller of the async method.

One might think that, since methodA() is async, once methodB reaches the await methodA(), then it might return control to the caller and allow to run code D in parallel to code A and code A'.

The essence is if method A constitutes an asynchronous operation as described in the documentation, or only until it reaches truly async code (code that runs on another thread) then it actually allows control to return to the caller (which in this case is code A').

My understanding is that asynchronous operation is an operation that will run on another thread, is this correct? So things like Task.Run, Task.Delay, Task.Sleep..? And that even though code A is within an async method, it is not an async operation and control does not go immediately back when awaiting methodA. Is this correct?

Just for completion, check this question where in the answer's comments they argue precisely about this, without a very clear answer.

EDIT: This is not a duplicate, the other question asks about why is certain code awaited or not, I am asking what methods do actually spin a Task. Since the await on a method technically can run completely sync if there is no code returning a task (as answered in the other questions), I am asking what methods are actually async. So at what point the code returns the Task. Not how the sequence of things execute, but what will it make it execute async.


Solution

  • [...], control returns to the caller of the async method.

    This sentence had confused the hell out of me when I was learning asynchronous programming. What it means is that the Task is created and returned. You can think the asynchronous methods as generators for Task objects. The async method has to do some work in order to create the Task. This work completes when the first await of an incomplete awaitable inside the method is reached. At that point the Task is created, it is handed to the caller of the method, and the caller can do whatever it wants with it.

    Usually the caller awaits the task, but it can also do other things before awaiting it, or it might never await it and completely ignore it, although this is not recommended. Generally you want to keep track of your tasks, and not let them run unattended in a fire-and-forget fashion.

    When the caller does not await immediately the returned task, and the task was not in a completed state upon creation, you introduce concurrency in your application: More than one things might be happening at overlapping time spans. You can read about the differences between concurrency and parallelism here.