async await: is the main thread suspended?
Asked Answered
O

4

10

I was reading about async/await keywords and I've read that:

When the flow of logic reaches the await token, the calling thread is suspended until the call completes.

Well, I've created a simple windows forms application, placed two labels, a button and a textbox and I wrote the code:

        private async void button1_Click(object sender, EventArgs e)
        {
            label1.Text = Thread.CurrentThread.ThreadState.ToString();
            button1.Text =  await DoWork();
            label2.Text = Thread.CurrentThread.ThreadState.ToString();
        }

        private Task<string> DoWork()
        {
            return Task.Run(() => {
                Thread.Sleep(10000);
                return "done with work";
            });            
        }

What I don't understand is that when I click the button, the label1 will have the text Running and the label will have the same text only after 10 seconds, but in these 10 seconds I was able to enter the text in my textbox, so it seems that the main thread is running...

So, how does the async/await works?

here is a "screenshot" from the book: enter image description here

Regards

Orthogenic answered 6/6, 2016 at 21:58 Comment(15)
You aren't using async/await in your example and are running on a seperate thread (Task.Run runs from the thread pool).Tamathatamaulipas
But why the label2 text is set after 10 seconds? this means that I wait on the main thread 10 seconds and why in these 10 seconds I am able to enter text in my textbox?Orthogenic
Question still unclear.. Also i can't find the connection between DoWork and the button event, moreover you haven't exposed the GetText method. Additionally, "the same text only after 10 seconds" is a bit murky. "i was able to enter text in my textbox" you are never limited to this option. Please edit and explain your issue into more detail.Larry
sorry, I've updated the code... before I was using another methodOrthogenic
compare tid from an async handler with a non-async one ...Gullible
When the flow of logic reaches the await token, the calling thread is suspended until the call completes. - where did you get this quote from? This is almost never correct, at best it depends on the thread's synchronization context.Psychognosis
@Noseratio I've read this in Pro C# 5.0 and the .NET 4.5 Framework by Andrew Troelsen (chapter 19, page 746)Orthogenic
@BudaGavril: literally, a thread is suspended with Thread.Suspend API. This is certainly not the case with await, unless some special efforts are taken. Perhaps, the author implied here something like logical execution flow rather than thread.Psychognosis
I have just started reading the section of the book you reference and ABSOLUTELY EVERYTHING IS WRONG. Throw this away and start over.Trimetallic
Maybe this is offtopic, but can you recommend me a good book with a complete .net framework reference?Orthogenic
The framework reference is MSDN; you'll do a lot better to just browse the web site than to get a paper copy of the framework documentation. For a good book on C#, all the standard books are great: C# in Depth, Essential C#, C# in a Nutshell. I edited all three; they are accurate.Trimetallic
Thanks, I'll start to read those booksOrthogenic
It's just so awful. "This will cause the calling thread to sleep" -- no, it causes the worker thread to sleep. "I'll put this string into a new task" -- no, the task already created by Run will obtain the string. Whoever wrote this thing is just making stuff up. None of this is at all correct. It's like the author has thought "how would I implement this feature?" and described that, rather than describing the actual feature as we implemented it.Trimetallic
@BudaGavril: I have an async intro blog post that may help you, where I try to introduce how to think about async/await without overwhelming you.Policewoman
I'll take a look. ThanksOrthogenic
T
22

I've read that: When the flow of logic reaches the await token, the calling thread is suspended until the call completes.

Where did you read that nonsense? Either there is some context there that you're not quoting, or you should stop reading whatever text it is that contained this. The point of await is to do the opposite of that. The point of await is to keep the current thread doing useful work while the asynchronous task is in flight.

UPDATE: I downloaded the book you referenced. Absolutely everything in that section is wrong. Throw this book away and buy a better book.

What I don't understand is that when I click the button, the label1 will have the text Running and the label will have the same text only after 10 seconds, but in these 10 seconds I was able to enter the text in my textbox, so it seems that the main thread is running...

That's correct. Here's what happens:

        label1.Text = Thread.CurrentThread.ThreadState.ToString();

The text is set.

        button1.Text =  await DoWork();

A bunch of stuff happens here. What happens first? DoWork is called. What does it do?

        return Task.Run(() => { Thread.Sleep(10000);

It grabs a thread out of the thread pool, puts that thread to sleep for ten seconds, and returns a task representing the "work" being done by that thread.

Now we are back here:

        button1.Text =  await DoWork();

We have a task in hand. Await first checks the task to see if it is already complete. It is not. Next it signs up the remainder of this method as the continuation of the task. Then it returns to its caller.

Hey, what is its caller? How did we get here anyways?

Some code called this event handler; it was the event loop that is processing Windows messages. It saw a button was clicked and dispatched to the click handler, which has just returned.

Now what happens? The event loop keeps running. Your UI keeps on running nicely, as you noticed. Eventually that thread ticks off ten seconds and the task's continuation is activated. What does that do?

That posts a message into the Windows queue saying "you need to run the rest of that event handler now; I have the result you were looking for."

The main thread event loop eventually gets to that message. So the event handler picks up where it left off:

        button1.Text =  await DoWork();

The await now extracts the result from the task, stores it in the button text, and returns back to the event loop.

Trimetallic answered 6/6, 2016 at 22:56 Comment(3)
I've attached a "screenshot" from the book... Maybe I got this wrong... So long story short: The UI thread keeps running, the execution of the code is suspended when the await keyword is met in the code and continues when the task is done, right?Orthogenic
@BudaGavril: The book is completely and utterly wrong from start to finish in the section on async-await. The author has completely misunderstood it. Throw this book away and get a better book.Trimetallic
@BudaGavril: Correct: the execution of the event handler is suspended when the await returns control to the caller, and is resumed at some time after the task completes. The execution of the UI thread continues as it always does.Trimetallic
T
0

async/await creates a state machines that handles the continuation for you. A very rough equivalent (without a bunch of features) is an explicit continuation method, for instance:

private void button1_Click_Continuation(string value)
{
    button1.Text = value;
    label2.Text = Thread.CurrentThread.ThreadState.ToString();
}

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = Thread.CurrentThread.ThreadState.ToString();
    DoWork(button1_Click_Continuation);
}

private void DoWork(Action<string> continuation)
{
    Task.Run(() => {
        Thread.Sleep(10000);
        continuation("done with work");
    });
}

Note that I am still using Task.Run to run on a seperate thread. Note that this version has no way of tracking progress (while the original could change the return value of button1_Click to Task to see if it has completed or not).

In addition to the above transformation the system is smart about whether a Task started on the UI thread and marshalls back to the UI thread again to ensure everything works as you expect. This is probably the main thing you didn't realize but expanding on the state machine aspect sometimes explains what asyc/await really means (instead of leaving it as mystical).

Tamathatamaulipas answered 6/6, 2016 at 22:15 Comment(3)
So you're saying that the code from the click event runs on another thread ? Because this would explain why I was able to enter text in my textbox. But how about updating the UI from a secondary thread? Is there some documentation that says that I can update UI with no issues only from async methods (without other workarounds) giving the fact that you're updating the UI from a secondary thread?Orthogenic
@BudaGavril: No, absolutely not. The code from the click event runs on the UI thread; it's a UI event handler! The await returns control to the message loop; that's why your UI does not freeze. You may not update the UI from a second thread; you must only update the UI from the UI thread. Await makes it easier to do that because it lets you more easily express asynchronous workflows that stay on the UI thread, or where the continuations are automatically marshaled back.Trimetallic
@BudaGavril: This answer is basically correct but the last paragraph is extremely important. The call to continuation in the Task.Run does not actually invoke the continuation directly; it causes the continuation to run back on the UI thread, where it belongs. Note that in a console application, this is not true; the continuation could be scheduled onto a worker thread.Trimetallic
V
0

By writing await, you are saying - please stop execution of the method at this point, wait for DoWork to finish and only then continue.

Asynchronous Programming with async and await (C#) in What Happens in an Async Method section has a step by step explanation, with a picture, of what happens in a async method.

An even better explanation is at await (C# reference). Have a look at comments for WaitSynchronously and WaitAsynchronouslyAsync below.

The article also states (emphasis mine):

The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. The task represents ongoing work.

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Call the method that runs synchronously.
    //string result = await WaitSynchronously ();

    // Display the result.
    textBox1.Text += result;
}

// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000);
    return "Finished";
}

// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
    // Add a using directive for System.Threading.
    Thread.Sleep(10000);
    return "Finished";
}
Viradis answered 6/6, 2016 at 22:33 Comment(2)
so the await method suspends the execution of the method, but the main thread (UI) is still running? So "When the flow of logic reaches the await token, the calling thread is suspended until the call completes." is only partial true? I mean that not the thread is suspended, but only the code execution from the method?Orthogenic
@BudaGavril: await suspends the execution of the method by (1) signing up the remainder of the method as the continuation of the task, and (2) returning. All this work is done on the UI thread. The UI thread never stops running. The statement is not partially true; it is completely false from start to finish. The function of await is to prevent the UI thread from blocking, not to cause it to block.Trimetallic
B
0

Basically the crux is the program execution continues in synchronous manner until or unless await is encountered. once await is encountered, suppose for a Task A, the compiler will switch back to the main method which called this asynchronous method without the await keyword and will continue the program executing from calling point of Task A as compiler knows it has to wait for the completion of Task A so why not to complete its other pending task.

Here what is happening is button1_Click event is not awaited in main method so it will continue to execute once await DoWork() is encountered. And after DoWork() is completed compliler will continue to execute the further code in button1_Click

Brachiate answered 21/12, 2020 at 11:53 Comment(2)
"button1_Click event is not awaited in main method" <== are you referring to the static void Main(string[] args) entry point of the application?Gussy
yes. depending on the type of application. Basically from where ever button1_Click is called there it is not awaited.Brachiate

© 2022 - 2024 — McMap. All rights reserved.