实际上,异步/等待并不是那么神奇。整个主题非常广泛,但是对于您的问题,我想我们可以解决,但需要快速而完整的答案。
让我们处理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
不创建新线程,它将如何做呢?
好吧,发生的是您的方法被一分为二。这是那些广泛的话题类型之一,因此,我不会赘述过多,但足以说明该方法分为以下两件事:
- 直至的所有代码
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
关键字,一些聪明的编译器魔术一起,确实是它基本上像“好吧,你知道吗,我会简单地从这里按钮单击事件处理程序返回,当你(在,事情我们”重新等待),直到完成为止,让我知道,因为我还有一些代码需要执行。”
实际上,它将使SynchronizationContext类知道已完成,这取决于当前正在运行的实际同步上下文,将排队等待执行。Windows Forms程序中使用的上下文类将使用消息循环正在泵送的队列对其进行排队。
因此,它返回到消息循环,现在可以自由继续发送消息,例如移动窗口,调整窗口大小或单击其他按钮。
对于用户而言,UI现在可以再次响应,可以处理其他按钮单击,调整大小,最重要的是可以重新绘制,因此它似乎不会冻结。
- 2秒后,我们等待的事情完成了,现在发生的事情是,它(同步上下文)将一条消息放入了消息循环正在查看的队列中,并说:“嘿,我有更多的代码可以您执行”,而这段代码就是等待后的所有代码。
- 当消息循环到达该消息时,它基本上会在中断之后的地方重新“进入”该方法,
await
然后继续执行该方法的其余部分。请注意,此代码再次从消息循环中调用,因此,如果此代码恰好长时间执行而未async/await
正确使用,它将再次阻塞消息循环
这里有很多活动部件,所以这里有一些指向更多信息的链接,我想说的是“您是否需要它”,但是这个主题涉及面很广,了解其中一些活动部件非常重要。您将始终了解异步/等待仍然是一个漏水的概念。一些潜在的局限性和问题仍然会泄漏到周围的代码中,如果没有,您通常最终不得不调试似乎无缘无故地随机中断的应用程序。
好的,如果GetSomethingAsync
启动一个将在2秒内完成的线程该怎么办?是的,那么显然有一个新的线索在起作用。但是,此线程不是由于此方法的异步性,而是因为此方法的程序员选择了一个线程来实现异步代码。几乎所有异步I / O 都不使用线程,它们使用不同的东西。async/await
本身不会增加新线程,但是显然“等待的事情”可以使用线程来实现。
.NET中有很多东西不一定会自己增加线程,但仍然是异步的:
- Web请求(以及许多其他与网络相关的事情,这需要时间)
- 异步文件读写
- 还有一个很好的信号是,所讨论的类/接口是否具有名为
SomethingSomethingAsync
or的方法BeginSomething
,EndSomething
并且IAsyncResult
涉及其中。
通常,这些东西不使用引擎盖下的螺纹。
好吧,所以您想要一些“广泛的主题”?
好吧,让我们向Roslyn咨询一下我们的按钮单击:
尝试罗斯林
我不会在这里链接完整的生成类,但这是很糟糕的东西。