一次又一次,我看到它说使用async-await不会创建任何额外的线程。这没有任何意义,因为计算机似乎一次可以做不止一件事情的唯一方法是
async
await
因此,如果async-await两者都没有,那么它如何使应用程序响应?如果只有 1 个线程,那么调用任何方法意味着在执行其他任何操作之前等待该方法完成,并且该方法中的方法必须等待结果才能继续,等等。
实际上,async/await 并没有那么神奇。完整的主题非常广泛,但我认为我们可以管理您的问题的快速而完整的答案。
让我们在 Windows 窗体应用程序中处理一个简单的按钮单击事件:
public async void button1_Click(object sender, EventArgs e) { Console.WriteLine("before awaiting"); await GetSomethingAsync(); Console.WriteLine("after awaiting"); }
我将 明确地 不 谈论它现在GetSomethingAsync正在返回的任何内容。假设这将在 2 秒后完成。
GetSomethingAsync
在传统的非异步世界中,您的按钮单击事件处理程序将如下所示:
public void button1_Click(object sender, EventArgs e) { Console.WriteLine("before waiting"); DoSomethingThatTakes2Seconds(); Console.WriteLine("after waiting"); }
当您单击表单中的按钮时,应用程序将显示冻结大约 2 秒钟,而我们等待此方法完成。发生的事情是“消息泵”,基本上是一个循环,被阻塞了。
这个循环不断地询问窗口“有没有人做过什么,比如移动鼠标,点击了什么?我需要重绘什么吗?如果有,告诉我!” 然后处理那个“东西”。这个循环得到一条消息,用户点击了“button1”(或来自 Windows 的等效消息类型),并最终调用了我们button1_Click上面的方法。在此方法返回之前,此循环现在一直处于等待状态。这需要 2 秒,在此期间,不会处理任何消息。
button1_Click
大多数处理窗口的事情都是使用消息完成的,这意味着如果消息循环停止发送消息,即使只是一秒钟,用户也会很快注意到它。例如,如果您将记事本或任何其他程序移到您自己的程序之上,然后再次离开,则会向您的程序发送一系列绘图消息,指示窗口的哪个区域现在突然再次变得可见。如果处理这些消息的消息循环正在等待某事,被阻塞,则不进行绘制。
那么,如果在第一个示例中,async/await不创建新线程,它是如何做到的呢?
async/await
好吧,发生的事情是您的方法分为两部分。这是那些广泛的主题类型之一,所以我不会详细介绍,但足以说明该方法分为以下两部分:
插图:
code... code... code... await X(); ... code... code... code...
重新排列:
code... code... code... var x = X(); await X; code... code... code... ^ ^ ^ ^ +---- portion 1 -------------------+ +---- portion 2 ------+
基本上该方法执行如下:
到目前为止,我们仍然在对 button1_Click 的原始调用中,发生在主线程上,从消息循环中调用。 如果导致的代码await花费大量时间,UI 仍将冻结。在我们的例子中,没有那么多
实际上,它会让 SynchronizationContext 类知道它已经完成,这取决于当前正在播放的实际同步上下文,它将排队等待执行。Windows 窗体程序中使用的上下文类将使用消息循环正在抽取的队列对其进行排队。
对于用户而言,UI 现在再次响应,处理其他按钮单击、调整大小以及最重要的是 重绘 ,因此它看起来不会冻结。
这里有很多活动部件,所以这里有一些指向更多信息的链接,我想说“如果你需要它”,但这个主题 相当广泛,了解 其中一些活动部件 是相当重要的。你总是会明白 async/await 仍然是一个有漏洞的概念。一些潜在的限制和问题仍然会泄漏到周围的代码中,如果没有,您通常最终不得不调试一个看似没有充分理由随机中断的应用程序。
好的,那么如果GetSomethingAsync启动一个将在 2 秒内完成的线程呢?是的,那么显然有一个新线程在起作用。然而,这个线程并不是 因为 这个方法的异步性,而是因为这个方法的程序员选择了一个线程来实现异步代码。几乎所有异步 I/O 都不 使用线程,它们使用不同的东西。async/await 本身 不会启动新线程,但显然“我们等待的东西”可以使用线程来实现。
.NET 中有很多东西不一定会自行启动线程,但仍然是异步的:
SomethingSomethingAsync
BeginSomething
EndSomething
IAsyncResult
通常这些东西不使用引擎盖下的线程。