一尘不染

如果async-await没有创建任何其他线程,那么它如何使应用程序响应?

c#

一次又一次,我看到它说async- await使用不会创建任何其他线程。这是没有道理的,因为一台计算机似乎一次只能完成一件事情的唯一方法是

  • 实际上一次执行一件以上的事情(并行执行,使用多个处理器)
  • 通过计划任务并在它们之间进行切换来模拟它(做一点点A,一点点B,一点点A,等等)

因此,如果async-
await这些都不起作用,那么如何使应用程序响应呢?如果只有1个线程,则调用任何方法都意味着在完成其他操作之前先等待该方法完成,并且该方法内的方法必须在继续执行之前等待结果,依此类推。


阅读 336

收藏
2020-05-19

共1个答案

一尘不染

实际上,异步/等待并不是那么神奇。整个主题非常广泛,但是对于您的问题,我想我们可以解决,但需要快速而完整的答案。

让我们处理一个Windows Forms应用程序中的简单按钮单击事件:

public async void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before awaiting");
    await GetSomethingAsync();
    Console.WriteLine("after awaiting");
}

我要 明确 不是 谈论什么它GetSomethingAsync是返回现在。我们只说这将在2秒后完成。

在传统的非异步环境中,您的按钮单击事件处理程序将如下所示:

public void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before waiting");
    DoSomethingThatTakes2Seconds();
    Console.WriteLine("after waiting");
}

当您单击表单中的按钮时,该应用程序将冻结约2秒钟,而我们等待此方法完成。发生的事情是基本上阻塞了一个“消息泵”,使其成为一个循环。

该循环不断询问Windows:“是否有人做过某些事情,例如移动鼠标,单击某些东西?我需要重新粉刷一些东西吗?如果是,请告诉我!”
然后处理那个“东西”。此循环收到一条消息,提示用户单击“
button1”(或Windows中的等效消息类型),并最终调用了button1_Click上面的方法。在此方法返回之前,此循环现在一直处于等待状态。这需要2秒钟,在此期间,不会处理任何消息。

与窗口打交道的大多数事情都是使用消息完成的,这意味着,如果消息循环停止泵送消息,即使只是一秒钟,用户很快就会注意到它。例如,如果将记事本或任何其他程序移到自己程序的顶部,然后再次移开,则会向您的程序发送一连串的绘画消息,指示现在突然又可以看到的窗口区域。如果处理这些消息的消息循环正在等待某些东西(已阻塞),则不会完成绘制。

那么,如果在第一个示例中,async/await不创建新线程,它将如何做呢?

好吧,发生的是您的方法被一分为二。这是那些广泛的主题类型之一,因此我将不做过多的详细说明,但足以说明该方法分为以下两种情况:

  1. 直至的所有代码await,包括对GetSomethingAsync
  2. 以下所有代码 await

插图:

code... code... code... await X(); ... code... code... code...

重新排列:

code... code... code... var x = X(); await X; code... code... code...
^                                  ^          ^                     ^
+---- portion 1 -------------------+          +---- portion 2 ------+

基本上,该方法执行如下:

  1. 它执行一切,直到 await
  2. 它调用该GetSomethingAsync方法,该方法将执行其操作,并返回 将在未来2秒内完成的操作

到目前为止,我们仍然位于对message1调用的主线程上对button1_Click的原始调用内。
如果导致代码await花费大量时间,则UI仍将冻结。在我们的示例中,没有那么多

  1. 什么await关键字,一些聪明的编译器魔术一起,确实是它基本上像“好吧,你知道吗,我会简单地从这里按钮单击事件处理程序返回,当你(在,事情我们”重新等待),直到完成为止,让我知道,因为我还有一些代码需要执行。”

实际上,它将使 SynchronizationContext类知道已完成,这将根据当前正在运行的实际同步上下文排队等待执行。Windows
Forms程序中使用的上下文类将使用消息循环正在泵送的队列对其进行排队。

  1. 因此,它返回到消息循环,现在可以自由继续发送消息,例如移动窗口,调整窗口大小或单击其他按钮。

对于用户而言,UI现在可以再次响应,可以处理其他按钮单击,调整大小,最重要的是可以 重新绘制 ,因此它似乎不会冻结。

  1. 2秒钟后,我们等待的事情完成了,现在发生的事情是,它(同步上下文)将一条消息放入了消息循环正在查看的队列中,并说:“嘿,我还有更多代码可用于您执行”,而这段代码就是等待 的所有代码。
  2. 当消息循环到达该消息时,它基本上会在中断后的地方重新“进入”该方法,await然后继续执行该方法的其余部分。请注意,此代码再次从消息循环中调用,因此,如果此代码恰好长时间执行而未async/await正确使用,它将再次阻塞消息循环

这里有很多活动部件,因此这里有一些指向更多信息的链接,我想说的是“您是否需要它”,但是该主题涉及 很广,了解 其中一些活动部件
非常重要。您将始终了解异步/等待仍然是一个漏水的概念。一些潜在的局限性和问题仍然会泄漏到周围的代码中,如果没有,您通常最终不得不调试似乎无缘无故地随机中断的应用程序。


OK,那么如果GetSomethingAsync启动一个将在2秒内完成的线程该怎么办?是的,那么显然有一个新的线索在起作用。但是,此线程不是 由于
此方法的异步性,而是因为此方法的程序员选择了一个线程来实现异步代码。几乎所有异步I / O 都不
使用线程,它们使用不同的东西。async/await 本身 不会增加新线程,但是显然“等待的事情”可以使用线程来实现。

.NET中有很多东西不一定独立运行一个线程,但它们仍然是异步的:

  • Web请求(以及许多其他与网络相关的事情,这需要时间)
  • 异步文件读写
  • 还有更多的好兆头是,如果所讨论的类/接口具有名为SomethingSomethingAsyncBeginSomething和的方法,EndSomething并且IAsyncResult涉及其中。

通常,这些东西不使用引擎盖下的螺纹。


好吧,所以您想要一些“广泛的主题”?

好吧,让我们向Roslyn咨询一下我们的按钮单击:

尝试罗斯林

我不会在这里链接完整的生成类,但这是很糟糕的事情。

2020-05-19