-1

Say in button click event I have:

await someFn()

And in the someFn code, I have got some synchronous code and some asynchronous code.

As per online reading on this topic, they all seem to say that when await is encountered, control immediately goes back the caller (UI thread in this case) while the asynchronous waiting happens.

I haven't come across situation where it is explained what happens when there is some synchronous code just before the async code in the awaited function. Does the synchronous code execute before passing the control to the caller (UI thread)?

I know that- had it been run using await Task.Run(...) instead of await someFn() then it would execute in a separate thread.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
variable
  • 8,262
  • 9
  • 95
  • 215
  • Assuming someFn() is an async function, it'll be run in a separate thread anyway, won't it? Or is someFn() just a function that returns a Task but isn't async itself? – Rup Jul 24 '21 at 14:57
  • 2
    @Rup `won't it?` - no, [it won't](https://stackoverflow.com/q/17661428/11683). – GSerg Jul 24 '21 at 14:58
  • 1
    `when await is encountered, control immediately goes back the caller` - correct. That is, when an *incomplete* await is encountered. `Does the syncronous code execute before passing the control to the caller` - does *your* code that you have after `await someFn()` execute before that `await` completes? – GSerg Jul 24 '21 at 15:00
  • GSerg - by syncronous code I don't mean the code after await. But the code within the someFn – variable Jul 24 '21 at 15:09
  • 1
    If you don't specifically use threading in your application, all *your* code is synchronous. When you await a .Net method that makes an *async OS call* that call is async. async-await is not threading. As a matter of fact, [There is no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) during the await of a async call. – Erik Philips Jul 24 '21 at 15:11
  • "*when there is synchronous code along with async code in the awaited function*" <== are you interested to know about the simple case of the `someFn` containing just one synchronous code block followed by just one genuinely asynchronous operation, or you want also to cover the case of multiple alternating synchronous blocks/asynchronous operations inside the `someFn`? – Theodor Zoulias Jul 24 '21 at 15:13
  • @variable *Your* function is also *`someFn`* for someone else. If you understand how it works in your function, surely you understand how it works in `someFn`? – GSerg Jul 24 '21 at 15:15
  • Does this answer your question? https://stackoverflow.com/a/37419845/11683 – GSerg Jul 24 '21 at 15:18
  • @variable Yes it does. It even has a chart with sync code interleaved with async code. It's labeled "Illustration". After the illustration, there is a detailed workflow. – GSerg Jul 24 '21 at 15:21
  • 1
    @variable `Console.WriteLine` is sync code. – Erik Philips Jul 24 '21 at 15:21
  • I am changed my question as the existing question was answered by https://stackoverflow.com/questions/68498481/await-returns-the-control-to-caller-who-executes-synchronous-code-in-the-awaite – variable Jul 30 '21 at 06:42
  • @variable Have you checked [this](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model#BKMK_WhatHappensUnderstandinganAsyncMethod)? – Peter Csala Aug 03 '21 at 14:15

2 Answers2

5

It is true that "when await is encountered, control immediately goes back the caller", under the condition that the awaitable will not be completed at the await point. Otherwise (if the awaitable is already completed) the control will stay inside the method, by executing immediately the code that follows the await.

That's all that influences the behavior of the await operator: the result of the IsCompleted property of the awaiter. It doesn't matter if the method that produced the awaitable included synchronous code or not. The await doesn't know, and doesn't care. A synchronous code block inside the someFn will indeed make a difference regarding the responsiveness of your application, but that has nothing to do with the await. To understand why, it may help to rewrite your code in a more verbose but equivalent form. This:

await someFn();

...is equivalent to this:

Task t = someFn();
await t;

The UI of your application will freeze because the someFn() blocks, not because the await t blocks. Unless of course you have created some wicked task-like awaitable type, that blocks when the IsCompleted property of its awaiter is invoked.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • The purpose of await is that the UI thread doesn't block, so why do you say that UI will freeze? – variable Jul 24 '21 at 15:42
  • @variable the UI will freeze because presumably the `someFn` contains blocking code. And the `someFn` is invoked before the control reaches the `await` operator. – Theodor Zoulias Jul 24 '21 at 15:45
  • It should be noted that the duration of the *freeze* may be so fast, that a user will not see it. Under those circumstances, it still froze. – Erik Philips Jul 24 '21 at 15:46
  • Ok I thought as soon as 1st await is encountered, control is passed to UI thread. You mean to say it will not and execute sync code, then encounter the await and pass control to UI – variable Jul 24 '21 at 15:48
  • It's a little more complex than that. Just because you have an await, does not mean the thread is passed back to the UI. For example, if you await `A()` and A await `B()` and B calls `await File.WriteByesAsync()`, the `await` in `B()` *releases* the thread. When `await A()` is called the, the thread is not released, nor for `await B()`. – Erik Philips Jul 24 '21 at 15:54
  • Theodor - because the question was answered by https://stackoverflow.com/questions/68498481/await-returns-the-control-to-caller-who-executes-synchronous-code-in-the-awaite, I have changed and updated this question. – variable Jul 30 '21 at 06:43
  • Hi @variable. Changing drastically a question that has received answers is frowned upon here. I would suggest to rollback this question to the [revision 4](https://stackoverflow.com/revisions/68510977/4), and post the [current revision](https://stackoverflow.com/revisions/68510977/5) as a new question. – Theodor Zoulias Jul 30 '21 at 06:49
  • I have exceeded my number of question limit. Can I request to consider this please.? Otherwise I will have to wait for some days and I am very curious about it. – variable Jul 30 '21 at 06:51
  • @variable nope, sorry. I am trying to play by the site's rules! – Theodor Zoulias Jul 30 '21 at 06:52
  • @variable btw I would suggest to print the `Thread.CurrentThread.ManagedThreadId` in your experiments, when you log in the `Console`. This may help you get a better understanding about what's going on. – Theodor Zoulias Jul 30 '21 at 06:59
  • you are right, it is printing different thread ids. What is going on here? Is each task running on separate thread even when I don't use Task.Run? Can you tell me more about this please. I will post the question when SO allow me to. – variable Jul 30 '21 at 07:04
  • @variable personally in my multithreaded experiments I use a small helper method `ConsolePrint`, that is shown [here](https://stackoverflow.com/questions/56569216/can-i-make-sure-a-static-constructor-has-finished-before-i-create-an-instance/56578771#56578771). It prints the current time and thread id along with the message. Regarding your last experiment, you need to learn about the `SynchronizationContext` class. Check out [this](https://stackoverflow.com/questions/52686862/why-the-default-synchronizationcontext-is-not-captured-in-a-console-app) question, it might help. – Theodor Zoulias Jul 30 '21 at 07:10
  • 1
    Ok making sense now, later I will post the code and question for benefit of all. – variable Jul 30 '21 at 07:35
  • https://stackoverflow.com/questions/68590930/in-console-app-why-does-synchronous-blocking-code-represented-by-thread-sleep-b – variable Jul 30 '21 at 13:26
  • Theodor- please can you tell me that when control return to the caller, then in the case of windows form app or console app, does this run on the same thread as that called the async method? – variable Jul 31 '21 at 20:51
  • @variable yes, that's what *"return to the caller"* means, in all types of applications. The "caller" is the calling thread, the current thread. – Theodor Zoulias Jul 31 '21 at 23:39
  • `It doesn't matter if the method that produced the awaitable included synchronous code or not.` - doesn't this answer Link based on example in that answer https://stackoverflow.com/a/68499847/1779091 says that `The Console.Write line will always execute before "do some work". `. So what you are saying is conflicting. – variable Aug 20 '21 at 16:08
  • @variable in my answer I am referring to the behavior of the `await` operator. The phrase you've quoted from the [other answer](https://stackoverflow.com/questions/68499421/is-using-task-run-a-short-hand-for-async-await/68499847#68499847) comes from the first section (before the **Update**), where the `await` operator is not mentioned at all, so how are these two answers relevant? – Theodor Zoulias Aug 20 '21 at 16:32
0

If you see an await in your code, for example when you see var obj = await someFn();, then you can treat is as a placeholder for the following code:

var waitingTask task = someFn();
if (!task.IsCompleted) {
  var thisTask = [Magic]

  return thisTask.Task;
}

var obj = waitingTask.Result;

In reality the code is rewritten to something more complex, a so called state machine. The [Magic] part is hard to explain but what it does is capturing the current SynchronizationContext, the current line that is executing and the task it's waiting on.

As soon as the task that you are waiting on completes, it will try to restore this method on the captured SynchronizationContext and continue working. It's this SynchronizationContext that tells you if you are working on the UI thread or not. This is why it's advised to use ConfigureAwait(false) to avoid restoring on the UI thread if it's not needed, avoiding possible slow downs on the UI for executing code that does nothing with the UI.

The main point here is that you see that the code before an await is executed synchronously/immediately. Even the code inside the function that you are calling is executed synchronously/immediately until you reach an await in that function that is not completed. From this point on the stack 'rewinds', creating (returning) a new Task on each point that waits for the underlying code to complete. If the lowest task completes, only that task that waits for it is 'revived', all tasks above it still stay 'waiting' and only when that bottom function has reached it real end of the method will it 'revive' the task that is waiting for it's result, and so on to the top of the stack.

  • The `ConfigureAwait(false)` is not a reliable way to control which thread will execute the code that follows the `await`. It is only effective if the awaitable is not completed at the `await` point. If the awaitable is completed when the `await` is reached, the same thread that ran the code before the `await`, will also run to code after the `await`. In order to offload work to a `ThreadPool` thread, the correct and reliable way is the `Task.Run` method. – Theodor Zoulias Aug 03 '21 at 11:09
  • 1
    @TheodorZoulias: Indeed, the `ConfigureAwait(false)` is only for when the Task needs to be restored, this 'hint' is ignored when the awaited method is already in the completed state. I have included it for completeness because the original question mentions the UI thread. If you are sure that the function isn't calling anything on the UI and doesn't need to block it, then `Task.Run` is a good option. – Christophe Devos Aug 03 '21 at 13:08