一次又一次,我看到它说使用async-await不会创建任何额外的线程。这是没有道理的,因为计算机一次可以做多件事的唯一方法是
实际上同时做多件事(并行执行,使用多个处理器) 通过调度任务并在它们之间切换来模拟它(做一点a,一点B,一点a,等等)。
因此,如果async-await都不做这些,那么它如何使应用程序具有响应性呢?如果只有1个线程,那么调用任何方法都意味着在执行其他操作之前等待该方法完成,并且该方法中的方法必须在继续执行之前等待结果,以此类推。
一次又一次,我看到它说使用async-await不会创建任何额外的线程。这是没有道理的,因为计算机一次可以做多件事的唯一方法是
实际上同时做多件事(并行执行,使用多个处理器) 通过调度任务并在它们之间切换来模拟它(做一点a,一点B,一点a,等等)。
因此,如果async-await都不做这些,那么它如何使应用程序具有响应性呢?如果只有1个线程,那么调用任何方法都意味着在执行其他操作之前等待该方法完成,并且该方法中的方法必须在继续执行之前等待结果,以此类推。
当前回答
我不打算和Eric Lippert或者Lasse V. Karlsen等人竞争,我只是想让大家注意这个问题的另一个方面,我想这个问题没有明确提到。
单独使用await并不能让你的应用神奇地做出响应。如果不管你在方法中做什么,你正在等待的UI线程阻塞,它仍然会阻塞你的UI,就像不可等待的版本一样。
你必须编写你的awaitable方法,以便它产生一个新线程或使用一个完成端口之类的东西(它将在当前线程中返回执行,并在完成端口收到信号时调用其他东西来继续)。但这部分在其他答案中有很好的解释。
其他回答
我不打算和Eric Lippert或者Lasse V. Karlsen等人竞争,我只是想让大家注意这个问题的另一个方面,我想这个问题没有明确提到。
单独使用await并不能让你的应用神奇地做出响应。如果不管你在方法中做什么,你正在等待的UI线程阻塞,它仍然会阻塞你的UI,就像不可等待的版本一样。
你必须编写你的awaitable方法,以便它产生一个新线程或使用一个完成端口之类的东西(它将在当前线程中返回执行,并在完成端口收到信号时调用其他东西来继续)。但这部分在其他答案中有很好的解释。
实际上,异步等待链是由CLR编译器生成的状态机。
async await使用的线程是TPL使用线程池执行任务的线程。
应用程序没有被阻塞的原因是状态机可以决定执行哪个协同例程、重复、检查并再次决定。
进一步阅读:
异步和等待生成什么?
异步等待和生成的状态机
异步c#和f# (III.):它是如何工作的?-托马斯·佩特里塞克
编辑:
好的。看来我的阐述是不正确的。然而,我必须指出,状态机是异步等待的重要资产。即使你采用异步I/O,你仍然需要一个助手来检查操作是否完成,因此我们仍然需要一个状态机,并确定哪些例程可以一起异步执行。
这并没有直接回答问题,但我认为它提供了一些有趣的额外信息:
Async和await本身不会创建新线程。但是根据你在哪里使用async-await,在await之前的同步部分可以运行在不同的线程上,而在await之后的同步部分则可以运行在不同的线程上(例如ASP。NET和ASP。NET核心表现不同)。
在基于ui线程的应用程序(WinForms, WPF)中,您将在之前和之后处于同一个线程上。但是当您在线程池线程上使用async-await时,await之前和await之后的线程可能不相同。
关于这个话题的一个很棒的视频
以下是我对这一切的看法,它在技术上可能不是超级准确,但至少对我有帮助:)。
机器上基本上有两种类型的处理(计算):
发生在CPU上的处理 在其他处理器(GPU,网卡等)上发生的处理,让我们称之为IO。
因此,当我们编写一段源代码时,在编译之后,根据我们使用的对象(这是非常重要的),处理将受到CPU或IO的限制,事实上,它可以绑定到两者的组合。
一些例子:
如果我使用FileStream对象(它是一个流)的Write方法,处理将会说,1%的CPU绑定和99%的IO绑定。 如果我使用NetworkStream对象(它是一个流)的写方法,处理将会说,1%的CPU绑定,和99%的IO绑定。 如果我使用Memorystream对象(这是一个流)的写方法,处理将是100% CPU的限制。
因此,如您所见,从面向对象程序员的角度来看,尽管我总是访问Stream对象,但下面发生的事情可能在很大程度上取决于对象的最终类型。
现在,为了优化事情,如果可能和/或必要的话,有时能够并行运行代码(注意我不使用异步这个词)是有用的。
一些例子:
在桌面应用程序中,我想打印一个文档,但我不想等待它。 我的web服务器同时为许多客户端提供服务,每个客户端并行获取他的页面(不是序列化的)。
在async / await之前,我们基本上有两个解决方案:
线程。它相对容易使用,有Thread和ThreadPool类。线程只受CPU限制。 “旧的”Begin/End/AsyncCallback异步编程模型。这只是一个模型,它并没有告诉你你将会受到CPU或IO的限制。如果你看一下Socket或FileStream类,它是IO绑定的,这很酷,但我们很少使用它。
async / await只是一个基于Task概念的通用编程模型。对于CPU绑定的任务,它比线程或线程池更容易使用,而且比旧的Begin/End模型更容易使用。 然而,它“只是”一个超级复杂的功能齐全的包装。
因此,真正的胜利主要是在IO绑定任务上,不使用CPU的任务,但async/await仍然只是一个编程模型,它不能帮助你确定处理最终如何/在哪里发生。
这意味着它不是因为一个类有一个方法“DoSomethingAsync”返回一个任务对象,你可以假设它将是CPU绑定(这意味着它可能非常无用,特别是如果它没有取消令牌参数),或IO绑定(这意味着它可能是必须的),或两者的组合(因为模型是相当病毒式传播的,绑定和潜在的好处可以,最终,超级混合,不那么明显)。
所以,回到我的例子,在MemoryStream上使用async/await进行写操作将保持CPU限制(我可能不会从中受益),尽管我肯定会从文件和网络流中受益。
总结其他答案:
Async/await通常是为IO绑定任务创建的,因为通过使用它们,调用线程不需要被阻塞。这在UI线程的情况下特别有用,因为我们可以确保它们在执行后台操作时保持响应(比如从远程服务器获取数据)。
Async不创建自己的线程。调用方法的线程用于执行异步方法,直到它找到一个可等待对象。然后,同一线程继续执行异步方法调用之外的调用方法的其余部分。注意,在被调用的async方法中,从awaitable返回后,该方法的提醒可以使用线程池中的线程执行——这是唯一出现单独线程的地方。