一次又一次,我看到它说使用async
-await
不会创建任何额外的线程.这是没有道理的,因为一台计算机一次能做不止一件事情的唯一方式是
- 一次实际做不止一件事(并行执行,使用多个处理器)
- 通过安排任务并在它们之间切换(做一点a、一点B、一点a等)来模拟它
所以,如果async
-await
都不做这两件事,那么它如何让应用程序响应呢?如果只有一个线程,那么调用任何方法都意味着在执行任何其他操作之前等待该方法完成,并且该方法中的方法必须在继续之前等待结果,以此类推.
一次又一次,我看到它说使用async
-await
不会创建任何额外的线程.这是没有道理的,因为一台计算机一次能做不止一件事情的唯一方式是
所以,如果async
-await
都不做这两件事,那么它如何让应用程序响应呢?如果只有一个线程,那么调用任何方法都意味着在执行任何其他操作之前等待该方法完成,并且该方法中的方法必须在继续之前等待结果,以此类推.
实际上,async/await并没有那么神奇.完整的主题相当广泛,但对于你的问题,我认为我们可以快速而完整地给出答案.
让我们来处理Windows窗体应用程序中的一个简单按钮单击事件:
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秒钟,在此期间,没有消息被处理.
大多数处理windows的事情都是使用消息完成的,这意味着如果消息循环停止发送消息,即使只有一秒钟,用户也会很快注意到它.例如,如果将记事本或任何其他程序移到自己的程序上,然后又移走,则会向程序发送一系列绘制消息,指示现在突然再次可见的窗口区域.如果处理这些消息的消息循环正在等待某个被阻止的消息,则不会进行绘制.
那么,如果在第一个示例中,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
到await
的所有内容它调用GetSomethingAsync
方法,该方法完成它的任务,并返回something that will complete 2 seconds in the future
So far we're still inside the original call to button1_Click, happening on the main thread, called from the message loop. If the code leading up to 100 takes a lot of time, the UI will still freeze. In our example, not so much
await
关键字,再加上一些巧妙的编译器魔法,基本上是这样的:"好吧,你知道吗,我将从按钮点击事件处理程序返回.当你(比如,我们正在等待的事情)开始完成时,请告诉我,因为我还有一些代码要执行".
Actually it will let the 100 know that it is done, which, depending on the actual synchronization context that is in play right now, will queue up for execution. The context class used in a Windows Forms program will queue it using the queue that the message loop is pumping.
因此,它返回到消息循环,现在可以自由地继续发送消息,比如移动窗口、调整窗口大小或单击其他按钮.
For the user, the UI is now responsive again, processing other button clicks, resizing and most importantly, 100, so it doesn't appear to freeze.
await
之后"重新进入"它停止的方法,并继续执行该方法的其余部分.请注意,此代码再次从消息循环中调用,因此如果此代码在没有正确使用async/await
的情况下执行较长的操作,它将再次阻塞消息循环引擎盖下面有很多活动部件,所以这里有一些指向更多信息的链接,我想说的是"如果你需要的话",但这个主题is相当广泛,了解some of those moving parts相当重要.你总会明白async/await仍然是一个漏洞百出的概念.一些潜在的限制和问题仍然会泄漏到周围的代码中,如果没有,你通常不得不调试一个应用程序,该应用程序似乎没有什么好的理由会随机中断.
好的,那么如果GetSomethingAsync
转一个线程,将在2秒钟内完成呢?是的,那么显然有一条新的线索在起作用.然而,这个线程并不是这个方法的异步性,这是因为这个方法的程序员 Select 了一个线程来实现异步代码.几乎所有异步I/O don't都使用一个线程,它们使用不同的东西.async/await
by themselves不会产生新的线程,但显然"我们等待的东西"可以使用线程实现.
在.NET中,有许多事情不一定会自行启动线程,但仍然是异步的:
SomethingSomethingAsync
或BeginSomething
和EndSomething
的方法,并且涉及IAsyncResult
.通常这些东西不需要在引擎盖下使用线.
好的,那么你想要一些"宽泛话题"的东西吗?
好吧,让我们问Try Roslyn个关于我们的按钮点击:
我不打算在这里链接完整生成的类,但这是非常血腥的东西.