事实上,async/await 并没有那么神奇。完整的主题相当广泛,但为了快速而完整地回答您的问题,我认为我们可以做到。
让我们处理 Windows 窗体应用程序中的简单按钮单击事件:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
我要去明确地 not谈论无论是什么GetSomethingAsync
现在正在返回。假设这将在 2 秒后完成。
在传统的非异步世界中,按钮单击事件处理程序将如下所示:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
当您单击表单中的按钮时,应用程序将冻结大约 2 秒,同时我们等待此方法完成。所发生的情况是“消息泵”(基本上是一个循环)被阻塞。
这个循环不断地询问窗口“有没有人做了什么事情,比如移动鼠标、点击什么东西?我需要重新绘制一些东西吗?如果是这样,请告诉我!”然后处理那个“东西”。该循环收到用户单击“button1”的消息(或来自 Windows 的等效类型的消息),并最终调用我们的button1_Click
方法同上。在该方法返回之前,该循环现在一直处于等待状态。这需要 2 秒,在此期间不会处理任何消息。
大多数处理窗口的事情都是使用消息完成的,这意味着如果消息循环停止发送消息,即使只是一秒钟,用户也会很快注意到。例如,如果您将记事本或任何其他程序移到您自己的程序之上,然后再次移开,则会向您的程序发送一系列绘画消息,指示窗口的哪个区域现在突然再次变得可见。如果处理这些消息的消息循环正在等待某些内容并被阻塞,则不会完成任何绘制。
所以,如果在第一个例子中,async/await
不创建新线程,它是如何做到的?
好吧,发生的情况是你的方法被分成了两个。这是广泛主题类型的事物之一,因此我不会过多讨论细节,但足以说明该方法分为以下两部分:
- 导致的所有代码
await
,包括调用GetSomethingAsync
- 全部代码如下
await
插图:
code... code... code... await X(); ... code... code... code...
重新排列:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
基本上该方法执行如下:
-
它执行一切直到await
-
它称为GetSomethingAsync
方法,它做它的事情并返回未来 2 秒内完成的事情
到目前为止,我们仍然处于对 Button1_Click 的原始调用中,该调用发生在主线程上,从消息循环调用。如果代码导致await
花费很多时间,UI 仍然会冻结。在我们的例子中,没有那么多
-
什么是await
关键字,加上一些聪明的编译器魔法,所做的就是它基本上类似于“好吧,你知道吗,我将简单地从此处的按钮单击事件处理程序返回。当您(例如,我们正在等待的事情) for) 抽出时间来完成,请告诉我,因为我还有一些代码需要执行”。
实际上它会让SynchronizationContext 类 https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext(v=vs.110).aspx知道它已经完成,根据当前正在运行的实际同步上下文,它将排队等待执行。 Windows 窗体程序中使用的上下文类将使用消息循环泵送的队列对其进行排队。
-
因此它返回到消息循环,该循环现在可以自由地继续发送消息,例如移动窗口、调整窗口大小或单击其他按钮。
对于用户来说,UI 现在再次响应,处理其他按钮点击、调整大小,最重要的是,重画,所以它似乎没有冻结。
-
2 秒后,我们正在等待的事情完成,现在发生的事情是它(好吧,同步上下文)将一条消息放入消息循环正在查看的队列中,说“嘿,我还有一些代码你去执行”,这段代码就是全部代码after等待。
-
当消息循环到达该消息时,它基本上会在它离开的地方“重新进入”该方法,紧接着await
并继续执行该方法的其余部分。请注意,此代码再次从消息循环中调用,因此如果此代码碰巧在不使用async/await
正确的话,它会再次阻塞消息循环
这里有很多活动部件,所以这里有一些更多信息的链接,我想说“如果你需要它”,但是这个主题is相当广泛,了解这一点相当重要其中一些活动部件。您总是会明白 async/await 仍然是一个有漏洞的概念。一些潜在的限制和问题仍然会泄漏到周围的代码中,如果没有泄漏,您通常最终不得不调试一个看似没有充分理由随机中断的应用程序。
- 使用 Async 和 Await 进行异步编程(C# 和 Visual Basic) https://msdn.microsoft.com/en-us/library/hh191443.aspx
- SynchronizationContext 类 https://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext(v=vs.110).aspx
-
斯蒂芬·克利里 - 没有线索 http://blog.stephencleary.com/2013/11/there-is-no-thread.html 非常值得一读!
-
Channel 9 - Mads Torgersen:C# 异步内部 https://sec.ch9.ms/ch9/f586/30f29955-f1f7-4a25-a790-152d536df586/Mads-Torgersen-Inside-C-Async_mid.mp4 非常值得一看!
好吧,那如果GetSomethingAsync
启动一个将在 2 秒内完成的线程?是的,那么显然有一个新的线程正在发挥作用。然而,该线程不是because这个方法的异步性,是因为这个方法的程序员选择了一个线程来实现异步代码。几乎所有异步 I/Odon't使用线程,他们使用不同的东西。async/await
通过他们自己不启动新线程,但显然“我们等待的事情”可以使用线程来实现。
.NET 中有很多东西不一定会自己启动线程,但仍然是异步的:
- Web 请求(以及许多其他与网络相关的需要时间的事情)
- 异步文件读写
- 还有更多,一个好兆头是所讨论的类/接口是否具有名为的方法
SomethingSomethingAsync
or BeginSomething
and EndSomething
还有一个IAsyncResult
涉及。
通常这些东西在底层不使用线程。
好的,那么您想要一些“广泛的主题内容”吗?
好吧,我们问一下尝试罗斯林 http://tryroslyn.azurewebsites.net/关于我们的按钮点击:
尝试罗斯林 http://tryroslyn.azurewebsites.net/#K4Zwlgdg5gBAygTxAFwKYFsDcAoUlaIoYB0AKgBYBOqAhgCb5k0gDWIO2ADsAEYA2YAMYxBfZiBgBhGAG9sMBTG78hMAG4B7MHRgBZABQBKWfMUBfUwstLeA4cwQRhm7TB7BkyDRACMAfUk7Fn0NHgArVEFkGBBUCDpUSgAaGABRNTjkAEFKKAlUQ2s5RRKpbxANPlRiAHVKMDQAGUhUfQAiHlQAMw1qGBoAdxoG/DbDHFLFQeHogHFUZDgNdAXyfCyQR0EjCcmyiAqq2vqmlvaaLrRKfqGR6DHdhQsS62U7GFJmFhh5xeXV9abJxGIrWErUZDASgQD5fYgAEVQYgQ+gATAAGTHjawWMxAA=
我不会在这里链接完整生成的类,但它是非常血腥的东西。