异步等待和线程

7

根据我在MSDN上阅读的内容

async和await关键字不会创建额外的线程。异步方法不需要多线程,因为异步方法不在自己的线程上运行。该方法在当前同步上下文中运行,并且仅在方法处于活动状态时才使用线程上的时间。您可以使用Task.Run将CPU绑定的工作移动到后台线程,但是后台线程无法帮助等待结果可用的进程。

基本上它是说不会创建线程。但是在我继承的类HttpClient中,我发现了一些有趣的东西...

async Task<T> SendAsync<T>(HttpMethod method, Uri uri, HttpContent content, CancellationToken cancellationToken) 
{
        HttpRequestMessage msg = new HttpRequestMessage(method, uri); 
        msg.Content = content;
        //On Main Thread
        HttpResponseMessage response = await SendAsync(msg, cancellationToken);
        //On worker thread
        //...
}

该方法在 static void Main 内部被调用。
Task result = client.GetAsync<string>(...);
//GetAsync calls await SendAsync<T>
result.Wait();

为什么在 await SendAsync 调用后我会进入一个单独的线程??我认为异步不会创建新的线程。或者至少在 await 后会回到原始线程。


1
如果您没有同步上下文,则来自异步方法的继续将在线程池线程上运行。控制台应用程序(我假设这是,因为您引用了“Main()”)默认情况下没有同步上下文。 - Glorin Oakenfoot
3个回答

16

文档中很少提到这一点,但是在控制台应用程序中,async/await的工作方式与UI应用程序非常不同,这是由于控制台应用程序中缺乏同步上下文。 此文章 详细描述了如何添加同步上下文以使async/await的行为更加可预测。

通常情况下,你对于async/await不一定涉及多线程是完全正确的(虽然像Task.Run之类的操作确实会导致相关代码在线程池中运行),但是(正如我链接的文章所述)在控制台应用程序中async/await可以在任何地方运行。

当async在同一个线程上运行时,我的通常比喻是想象去一家餐厅和其他9个人(总共10个人)。当服务员来时,有9个人准备好了,第10个人还没有准备好。在这种情况下,服务员将首先处理另外9个人的订单,然后再回到第10个人。如果由于某种原因第10个人准备得特别慢,服务员可以把订单带回厨房,等第10个人准备好后再回来。

显然,为了等待第10个人准备好点餐而引进第二个服务员是没有意义的,让其他人等待一个人是非常低效的。因为延迟不是由于缺乏服务员而引起的,而是由于第10个人准备得慢(这是服务员无法控制的)。


11

我在我的介绍异步编程的博客文章中描述了这种行为。

总之,这是正确的:

asyncawait关键字不会导致创建额外的线程。

这也是正确的:

我认为async不会创建新线程。

这是错误的

或者至少在await之后它将被调用回原始线程。

asyncawait- 仅仅是它们自己 - 不会导致创建任何其他线程。但是,在await完成后,async方法的其余部分必须在某个位置 执行。

默认情况下,await捕获“上下文”,即当前的SynchronizationContext(除非它是null,在这种情况下,当前上下文是当前的TaskScheduler)。UI线程有自己的同步上下文(例如WinFormsSynchronizationContextDispatcherSynchronizationContext),在这种情况下,async方法将继续在同一UI线程上执行。

在控制台应用程序中,没有同步上下文,因此await捕获的“上下文”将是线程池上下文。因此,当async方法准备恢复时,它将被调度到线程池中并由其中某个线程拾取。


1
控制台应用程序具有线程池同步上下文,而不是 GUI 和 ASP.Net 应用程序中使用的一次只处理一个块的同步上下文,因此当 await 完成时,它会在线程池线程上安排异步方法的其余部分。阅读 this article 以获取有关异步等待行为的更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接