-1

When await is encountered, control goes back to caller, while the awaited task runs (makes/waits for network request/response) in background. I know that awaiting task will not need thread when it is awaiting for network response as it is not really running anything but just waiting.

I want to ask - suppose in the awaited function, there is some synchronous code like Console.WriteLine, then because control has gone back to the caller, who executes the console.writeline?

Assume button click event does await Fn1();.

Example 1 :

async Task<string> fn1() {
    string result = await NetworkCallAsync('http......');
    return result;
}

In above example upon encountering await Fn1(), control goes back to the UI thread, while the network call is made. I understand this. When network call completes, the Ui thread will execute the rest of the code. That is - fetch the result and return.

Example 2:

async Task<string> fn1() {
    Console.WriteLine("hello"); //or any other synchronous code like Thread.Sleep(10);
    string result = await NetworkCallAsync('http......');
    return result;
}

In above code, when the button click encounters the await Fn1() and has returned control to the UI thread, then who executes the Console.WriteLine (or Thread.Sleep(10)) as shown in above example)?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
variable
  • 8,262
  • 9
  • 95
  • 215
  • 2
    This should be of interest: https://stackoverflow.com/questions/37419572/if-async-await-doesnt-create-any-additional-threads-then-how-does-it-make-appl – Rand Random Jul 23 '21 at 11:34
  • 7
    Possibly an interesting read: https://blog.stephencleary.com/2013/11/there-is-no-thread.html –  Jul 23 '21 at 11:40
  • `Function fn1() {` <== that's not a valid C# method signature. Could you fix the code so that it compiles? – Theodor Zoulias Jul 27 '21 at 07:03

2 Answers2

2

A task does not necessarily need any thread to execute. A task may represent an IO-operation, i.e. disk or network access. If so the task can use the asynchronous features in the OS to avoid using any thread at all while waiting.

If you are starting a task yourself you can specify a taskScheduler that determines what thread to use. Typically either the OS thread or an arbitrary threadpool thread.

It is also possible for tasks to complete synchronously, either because the result is already available, or because the method need to fulfill an API, and using real async IO is to much work.

As the user of a task you should in general not need to care how the task was run, just that it has completed.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Suppose there is some code steps (like console.write) in the awaited function, along with another await, then does that sync code execute before passing control to caller? – variable Jul 24 '21 at 20:01
  • @variable The method call will continue on the current thread up to the first await. But it is not wise to make to many assumptions about unknown code. You need to read the documentation for the specifications for any async code. – JonasH Jul 25 '21 at 19:52
  • In my example, the first await is `await f1()` in the button click event. So do you mean to say call will continue upto the last await? I'm confused as you said first await in your comment above. – variable Aug 20 '21 at 15:59
  • @variable, `Fn1()` will be called just like any other function. This will run the `WriteLine` and `NetworkCallAsync` just as normal. Only after `NetworkCallAsync` return does the await-part come into play. At that time if will check if the task is completed, and if not, return a new task that represents the result of the method. – JonasH Aug 20 '21 at 16:51
  • So should your answer say - final await? – variable Aug 20 '21 at 16:56
  • @variable no, a method can contains multiple awaits, when the first await in a method is run, the method may return, depending on the status of the awaited task. Note that each method is independent, and async/await is only a compiler aid to rewrite a method into a statemachine. – JonasH Aug 20 '21 at 17:04
  • How about saying the final `nested` await assuming the result is never immediately ready? – variable Aug 20 '21 at 17:08
  • @variable It does not really make much sense to talk about awaits between different methods, after all, a called method may or may not contain any awaits at all. – JonasH Aug 20 '21 at 17:23
1

The Console.WriteLine("hello"); will be invoked on the same thread where the Fn1() is invoked. The Fn1() will not return a Task<string> before executing all code that comes before the first await inside the Fn1. The Console.WriteLine("hello"); line is located before the first await, so it will run synchronously on the calling thread.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Often I read control goes to caller upon await. They do not say what happens for:1. nested awaits, 2. When there is sync code in the nested await followed by async code. It was nice to learn that just because there is await it will not return control to the caller. It will execute the syncronous code before the await asyncFn() and go to the inner most nested await and only then return control to the caller while awaiting the inner most await. The control will go to the caller all the way up upto the main thread. Then, when the inner most await is ready, control will go to await one level above – variable Jul 29 '21 at 19:03
  • @variable the `await` does indeed return the control to the caller (provided that the awaitable is not completed at the moment), but the `Fn1()` invocation comes before the `await`. The `await` is an operator, so it needs something to operate on, and that something must first be created. Compare it for example with `int result = - GetValue()`. You wouldn't expect that the minus operator will be invoked *before* invoking the `GetValue` method. Why would you expect it for `await`? – Theodor Zoulias Jul 29 '21 at 19:41
  • Another thing I'd like to add is that it is often said that when an async fn is called without await, then control immediately goes to the next line without waiting for the code inside the async fucntion to complete. This is not true suppose if within the async fn there is some syncronous code prior to await. Now upon encountering await in the asyncfn, it will give control to the caller and caller will not wait as there is no await. What I want to point out is that just because there is no await doesn't mean that code below this line will immediately run. – variable Jul 29 '21 at 20:11
  • @variable the reason they say that is because normally an asynchronous method does not contain synchronous blocking code. [Microsoft says](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#initiating-an-asynchronous-operation): "*An asynchronous method that is based on TAP can do a small amount of work synchronously, such as validating arguments and initiating the asynchronous operation, before it returns the resulting task. Synchronous work should be kept to the minimum so the asynchronous method can return quickly.*" – Theodor Zoulias Jul 29 '21 at 20:23
  • Right very nice to understand this. Thanks. One more thing - often I read that the db operation is io bound, where as long calculations are cpu bound. And it is recommended to use async await for the first case and async await + Task.Run() for the later case. I am now reached a stage where I'm trying to understand this from the view point of syncronous and asynchronous code. I think I will read more to get this part. – variable Jul 29 '21 at 20:34
  • @variable yeap, async/await is simple in it's usage, but surprisingly complex as a concept, and comes with a boatload of tiny nuances. It took me months to understand this little bugger! – Theodor Zoulias Jul 29 '21 at 20:43
  • Theodor - the first await is await f1() in the button click event- so your answer should say last await? – variable Aug 20 '21 at 15:57
  • @variable I am referring to the first `await` inside the `async` `f1` method. Whether the `f1` is called from another `async` method, and the resulting `Task` is awaited, is out of the scope of this answer. Each `async` method is an independent state machine. – Theodor Zoulias Aug 20 '21 at 16:23
  • 1
    Ok by last await I actually meant to say last nested await. Anyways I get the point much better now. Thank you for taking the time to answer. – variable Aug 21 '21 at 03:01