【问题标题】:What is executing the code when using await/async?使用 await/async 时执行的代码是什么?
【发布时间】:2016-11-08 23:42:21
【问题描述】:

这是来自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。

【问题讨论】:

标签: asp.net-mvc-4 async-await


【解决方案1】:

您对异步的理解有些偏离。它允许线程返回到池中如果它处于等待状态。最后一点很重要。线程仍在执行代码,只是一些外部进程正在进行,目前不需要该线程,即网络通信,文件I / O等。一旦外部进程完成,操作系统负责恢复控制到原始进程,然后需要从池中请求一个新线程才能继续。

此外,仅仅因为您的代码的某些部分是异步的,并不意味着一切都发生异步:即只有其他异步的代码实际上符合异步条件。正如我上面所说,线程只有在处于等待状态时才会被释放;运行一些同步意味着它没有等待,因此不会被释放。此外,有些事情永远不会是异步的,即使您尝试异步运行它们。任何受 CPU 限制的东西都需要线程运行,因此线程永远不会进入等待状态,也永远不会被释放。

更新

假设似乎还有其他线程在实际处理异步工作,但事实并非如此。就像我在 cmets 中所说的,操作系统级别的工作非常技术性,但是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 结构(例如,任务实例和 IRP),它们都处于挂起/未完成状态。但是,没有线程被阻塞等待写入操作完成。 ASP.NET、BCL、操作系统和设备驱动程序都没有专门用于异步工作的线程。

当磁盘完成数据写入时,它会通过中断通知其驱动程序。驱动程序通知操作系统 IRP 已完成,操作系统通过完成端口通知 BCL。线程池线程通过完成从 WriteAsync 返回的任务来响应该通知;这反过来又恢复了异步请求代码。在这个完成通知阶段,有一些线程在很短的时间内“借用”了一些线程,但在写入过程中实际上没有线程被阻塞。

这个例子被大大简化了,但它跨越了要点:真正的异步工作不需要线程。实际推出字节不需要 CPU 时间。还有第二课要学。想想设备驱动程序的世界,设备驱动程序必须如何立即或异步处理 IRP。同步处理不是一种选择。在设备驱动程序级别,所有重要的 I/O 都是异步的。许多开发人员有一个心智模型,将 I/O 操作的“自然 API”视为同步的,而异步 API 是构建在自然同步 API 之上的一层。然而,这完全是落后的:事实上,自然 API 是异步的;它是使用异步 I/O 实现的同步 API!

【讨论】:

  • 好的,谢谢,所以在 asp.net 中,只有在使用异步时才会使用 IO 线程(如果不使用,IO 调用将由工作线程完成)对吗?
  • 没有。一点也不。如果处理请求的 web 服务器线程遇到异步代码,并进入等待状态,那么它将返回到池中。在所有这些过程中,就操作系统级别发生的事情而言,它具有很高的技术性,但可以说,最终,当异步操作完成时,会从 Web 服务器的池中请求另一个线程来完成对请求的处理。这里没有不同类型的线程。
  • 感谢更新,非常有趣。我还有 1 个问题:为什么 .net 应用程序默认不发布这个线程?保持线程挂起,什么都不做有什么意义?
  • 我不确定我是否理解这个问题。线程 is 已释放,除非它不能是因为您正在运行同步或工作受 CPU 限制。同样,线程必须空闲才能被释放。只有当它必须做的工作时才会举行。
猜你喜欢
  • 2018-12-15
  • 2015-07-04
  • 1970-01-01
  • 1970-01-01
  • 2019-01-10
  • 2022-01-22
  • 2019-05-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多