以下是https://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4
的示例代码public async Task<ActionResult> GizmosAsync()
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos", await gizmoService.GetGizmosAsync());
}
据我所知,async / await对于释放工作线程非常有用,因此它可以处理其他请求。但GetGizmosAsync不仅是对“HttpResponse.Content.ReadAsAsync”的调用(我猜这是由来自IO池的线程执行的),它还调用其他非异步代码,如“var uri = Util.getServiceUri(”Gizmos“); ”
执行什么“var uri = Util.getServiceUri(”Gizmos“);”如果它不是来自工作池的线程?这里没有IO。
答案 0 :(得分:2)
您对异步的理解有点偏差。它允许线程返回到池,如果它处于等待状态。最后一点很重要。线程仍在执行代码,只是某些外部进程正在进行,当前不需要该线程,即网络通信,文件I / O等。一旦外部进程完成,操作系统负责恢复控制到原始进程,然后需要从池中请求一个新线程继续。
另外,仅仅因为代码的某些部分是异步并不意味着一切都发生异步:即只有异步的其他代码并且实际上有资格异步。如上所述,线程只有在处于等待状态时才会被释放;运行某些同步意味着它不会等待,因此不会被释放。此外,即使您尝试将它们设置为异步,某些内容也永远不会异步。任何CPU绑定都需要线程运行,因此线程永远不会进入等待状态,永远不会被释放。
<强>更新强>
假设似乎是有一些其他线程实际上正在处理异步工作,但这不是正在发生的事情。就像我在评论中所说的那样,操作系统级别的工作非常技术性,但是this is the best simplified explanation I've found。本文的相关部分如下:后代:
线程如何进行异步工作?
我一直被问到这个问题。这意味着必须有某个线程阻塞对外部资源的I / O调用。因此,异步代码释放了请求线程,但只是牺牲了系统中其他位置的另一个线程,对吧?不,一点也不。
为了理解异步请求扩展的原因,我将跟踪异步I / O调用的(简化)示例。假设请求需要写入文件。请求线程调用异步写入方法。 WriteAsync由基类库(BCL)实现,并为其异步I / O使用完成端口。因此,WriteAsync调用作为异步文件写入传递给操作系统。然后,操作系统与驱动程序堆栈通信,传递数据以写入I / O请求数据包(IRP)。
这是事情变得有趣的地方:如果设备驱动程序无法立即处理IRP,它必须异步处理它。因此,驱动程序告诉磁盘开始写入并向操作系统返回“挂起”响应。操作系统将“挂起”响应传递给BCL,BCL将不完整的任务返回给请求处理代码。请求处理代码等待该任务,该任务从该方法返回不完整的任务,依此类推。最后,请求处理代码最终将一个不完整的任务返回给ASP.NET,并释放请求线程以返回到线程池。
现在,考虑系统的当前状态。已经分配了各种I / O结构(例如,Task实例和IRP),并且它们都处于挂起/未完成状态。但是,没有阻塞等待写操作完成的线程。 ASP.NET,BCL,操作系统和设备驱动程序都没有专门用于异步工作的线程。
当磁盘完成数据写入时,它会通过中断通知其驱动程序。驱动程序通知操作系统IRP已完成,操作系统通过完成端口通知BCL。线程池线程通过完成从WriteAsync返回的任务来响应该通知。这反过来又恢复了异步请求代码。在完成通知阶段,有很多线程“借用”了很短的时间,但在写入过程中没有实际阻止任何线程。
这个例子大大简化了,但它跨越了主要观点:真正的异步工作不需要线程。实际推送字节不需要CPU时间。还有一个需要学习的第二课。想想设备驱动程序世界,设备驱动程序必须如何立即或异步处理IRP。同步处理不是一种选择。在设备驱动程序级别,所有非平凡的I / O都是异步的。许多开发人员都有一个心理模型,它将I / O操作的“自然API”视为同步,将异步API作为构建在自然同步API上的层。但是,这完全是落后的:实际上,自然API是异步的;它是使用异步I / O实现的同步API!