【问题标题】:"Async All the Way Down": Well, what's all the way at the bottom? [closed]《Async All The Way Down》:嗯,最底层的一路是什么? [关闭]
【发布时间】:2017-01-03 07:50:20
【问题描述】:

我试图完全理解async-await,而我的理解中的差距之一是看到什么是“一路向下”。我创建了一个async 方法,它被另一个async 方法调用,等等,一直到我理解的模糊术语,例如“UI”或“可以处理多个请求的Web 服务器”。我该如何用技术术语来描述什么是“一路向下”?

让我们以网络服务器的第二个例子为例。假设我有一个控制器动作,比如

[HttpGet]
public async Task<IHttpActionResult> GetRecords()
{
    var records = await repository.GetRecordsFromDbAsync();
    return Ok(records);
}

在 .NET 源代码中哪里可以找到能够异步调用它的“一路向下”代码?

【问题讨论】:

标签: c# .net asynchronous async-await


【解决方案1】:

“一直向下异步”这句话有点误导,因为它通常指的是一旦你使用了异步方法,就需要一路异步方法向上(或 back,取决于您的心理形象)- 从您的异步方法到它的 调用者,然后是调用者的调用者,依此类推,一直返回。

您的示例显示了一个公开 async 任务的 WebApi/MVC 控制器。 async 链中的下一步是 WebApi/MVC 基础结构,它接收 HTTP GET 请求,将其映射到控制器,并将调用分派给控制器的方法。这个基础设施是async 感知的,这意味着它知道正确调用async 控制器方法和await 其结果以返回HTTP 响应。

至于该基础架构的具体实现方式,我不知道也不关心 - 我知道 ASP.NET Web 服务支持 async Task 控制器,这对我来说已经足够了。

【讨论】:

  • 我会说这句话的措辞是基于“它一直是海龟”,因为它支持类似的类比。
  • 没错,这就是它令人困惑的原因。
  • 您的回答具有误导性——您应该关心基础设施的实施方式。这是异步代码最大的警告之一,IMO。它应该是“一直向下向上异步”。原因是异步方法中的阻塞代码会阻塞当前上下文的执行see also here。允许异步操作阻塞执行线程违背了异步 i/o 的想法,这意味着无论您希望获得什么性能提升,它都只是由于使用了多个线程,这对于单线程应用程序来说意味着没有。跨度>
  • 我应该关心基础设施的合同和承诺。了解内部是如何实现的具有指导意义并且可以帮助调试,但就 async/await 而言,知道我的框架支持 async Task 入口点意味着我可以使用 async Task 入口点。
【解决方案2】:

一直到最后都是 Win32 API。在该级别,异步 I/O 是通过 OVERLAPPED 对象和可警报等待(例如 WaitForMultipleObjectsEx)完成的。

【讨论】:

【解决方案3】:

GetRecordsFromDbAsync 是一个异步方法,因此您的顶级异步方法(由支持异步的 ASP.NET Web 服务器调用)只是将其异步性移交给下一个级别。

一直到GetRecordsFromDbAsync 或其后代实际上调用了调用堆栈中的最后一个异步方法。在那里,它可能会变成原生的,并且会注册一个 I/O 中断或其他在读取文件、处理 Web 请求等时被调用的东西。

【讨论】:

    【解决方案4】:

    每一种过程编程语言都是一系列函数/过程调用,一个函数/过程调用另一个函数/过程。可以使用调用图see Wikipedida for a starting point for call graphs 来表示从一个过程到另一个过程的调用序列。这些图表通常显示从顶部开始到底部的过程调用顺序。

    我很快制作了一个 ASP .NET MVC 应用程序的部分调用图的图表。该图只是 ASP .NET MVC 应用程序的部分表示,因为它省略了诸如操作系统(例如 Windows)、Web 服务器(例如 IIS)和 ASP .NET 的各种组件的初始接收请求等内容。负责处理通过 ASP .NET 请求处理管道的 HTTP 请求。出于讨论的目的,可以省略这些事项。尽管值得注意的是,它们将位于调用图的顶部,因为它们处理处理 HTTP 请求的初始阶段,并最终在某个时刻 ASP .NET 最终调用控制器的操作。

    正如您在图表中看到的,我已经将 ASP .NET 调用的控制器动作表示为异步动作。调用图的左侧是一系列异步过程调用。最终它会到达作为您问题主题的框。

    与“一路向下”概念一致的问题的答案是“我是一个异步方法”。这个异步方法有什么作用?嗯,这取决于你想做什么?如果您正在读取或写入文件,那么它是读取或写入文件的异步调用。你在做数据库查询吗?然后调用的是进行该查询的异步方法。考虑到这一点,我猜你可以说通常底部是设备驱动程序方法,它异步执行 IO 访问。尽管它可能很容易成为您希望执行的长时间运行的计算绑定异步操作,例如处理图像或视频文件。

    这里也值得注意调用图的右侧。尽管通常您可能希望一直调用异步方法,但此调用图的右侧分支显示您根本不需要调用底部的异步方法。

    【讨论】:

      【解决方案5】:

      从您的问题中不清楚您在哪里找到该引文,或者在什么情况下使用它。但是,“一直异步”通常被理解为意味着您不应该在单个逻辑操作中在同步和异步方法之间来回切换。

      如果某事要异步完成,它应该是一致异步的,在所有级别——因此,“一直向下”(向上)层次结构/调用堆栈/等。换句话说,在异步代码中的任何时候进行同步的阻塞调用是没有意义的,因为它根本就不是真正的异步。

      来自 .NET 并行编程团队的博客文章,"Should I expose synchronous wrappers for asynchronous methods?"

      一直异步

      这里的重点是,在将异步 API 包装为同步 API 时需要格外小心,因为如果不小心,可能会遇到真正的问题。如果您发现自己认为需要从期望同步调用的事物中调用异步方法(例如,您正在实现一个具有同步方法的接口,但为了实现该接口,您需要使用仅异步暴露),首先确保它是真的,真的有必要;虽然包装“同步而不是异步”似乎更方便,而不是重新探测这个或那个代码路径以从上到下异步,但如果可能的话,重构通常是更好的长期解决方案。

      关于在 .NET 源代码中哪里可以找到它的问题,MSalters has already answered this。从根本上说,.NET API 将调用 Windows 操作系统提供的系统级 API。它们执行异步 I/O,并在它们完成时向调用者发出信号。不过,您实际上并不需要了解这在技术层面上是如何工作的;这就是抽象的重点。

      【讨论】:

      • 您提供的链接很有用。至于“一直向下异步”的来源,我怀疑它来自我很久以前遇到的 Stephen Cleary msdn.microsoft.com/en-us/magazine/jj991977.aspx 写的一篇文章。
      • 啊,这也是一个非常好的链接,@Darren。为什么它不包含在您的答案中? :-)
      • 因为我发现 Bob 叔叔在黑板上绘制的调用图更有助于理解 Cleary 文章中的意思。
      猜你喜欢
      • 2020-08-02
      • 1970-01-01
      • 1970-01-01
      • 2014-06-26
      • 2010-12-18
      • 1970-01-01
      • 2022-12-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多