async await:主线程是否挂起?

async await: is the main thread suspended?

我正在阅读有关 async/await 关键字的内容,我已经阅读了:

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

好吧,我创建了一个简单的 windows forms application,放置了两个标签、一个按钮和一个文本框,然后我编写了代码:

        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";
            });            
        }

我不明白的是,当我点击按钮时,label1 将具有文本 Running 并且标签将在 10 秒后具有相同的文本,但是在这 10 秒内我是能够在我的文本框中输入文本,所以主线程似乎是 运行...

那么,async/await 是如何工作的?

这是书中的 "screenshot":

此致

async/await 创建一个状态机来为您处理延续。一个非常粗略的等价物(没有一堆特征)是一个显式的延续方法,例如:

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");
    });
}

请注意,我仍在单独的线程上使用 Task.Run 到 运行。请注意,此版本无法跟踪进度(而原始版本可以将 button1_Click 的 return 值更改为 Task 以查看它是否已完成)。

除了上述转换之外,系统还会智能判断 Task 是否在 UI 线程上启动并再次编组回 UI 线程以确保一切正常预计。这可能是您没有意识到的主要事情,但在状态机方面的扩展有时会解释 asyc/await 的真正含义(而不是让它变得神秘)。

通过写 await,您是在说 - 请在此时停止执行该方法,等待 DoWork 完成,然后再继续。

Asynchronous Programming with async and await (C#) 异步方法中发生的事情 部分有一步一步的解释,并附有图片,说明 async 方法中发生的事情.

更好的解释在 await (C# reference)。查看下面 WaitSynchronouslyWaitAsynchronouslyAsync 的评论。

文章还指出(强调我的):

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";
}

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

你是从哪里读到这些废话的?要么那里有一些你没有引用的上下文,要么你应该停止阅读包含它的任何文本。 await 的目的是做相反的事情。 await 的要点是在异步任务运行时保持当前线程做有用的工作

更新:我下载了您提到的那本书。该部分中的所有内容绝对是错误的。把这本书扔掉,买一本更好的书。

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...

没错。事情是这样的:

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

文本已设置。

        button1.Text =  await DoWork();

这里发生了很多事情。首先会发生什么? DoWork 被调用。它有什么作用?

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

它从线程池中抓取一个线程,让该线程休眠十秒钟,然后 returns 代表该线程正在完成的 "work" 任务。

现在我们回到这里:

        button1.Text =  await DoWork();

我们手头有任务。 Await 首先检查任务是否已经完成。它不是。接下来它将此方法的其余部分注册为任务的 continuation。然后它 returns 给它的调用者。

嘿,它的调用者是什么?我们是怎么到这里的?

一些代码调用了这个事件处理程序;事件循环正在处理 Windows 消息。它看到一个按钮被点击并被分派给刚刚返回的点击处理程序。

现在发生了什么?事件循环保持运行。正如您所注意到的,您的 UI 保持 运行 很好。最终该线程结束 10 秒,任务的继续被激活。那有什么作用?

向 Windows 队列发送消息说 "you need to run the rest of that event handler now; I have the result you were looking for."

主线程事件循环最终到达该消息。所以事件处理程序从它停止的地方开始:

        button1.Text =  await DoWork();

await 现在从任务中提取结果,将其存储在按钮文本中,然后 returns 返回事件循环。

基本上,关键是程序以同步方式继续执行,直到或除非遇到 await。一旦遇到 await,假设对于任务 A,编译器将切换回调用此异步方法的 main 方法 不带 await 关键字 并将继续从任务调用点执行程序A 因为编译器知道它必须等待任务 A 完成,所以为什么不完成它的其他未决任务。

这里发生的事情是 button1_Click main 方法中没有等待事件,因此一旦遇到 await DoWork() 它将继续执行。在 DoWork() 完成后,编译器将继续执行 button1_Click

中的进一步代码