【问题标题】:Asynchronous methods of ApiController -- what's the profit? When to use?ApiController 的异步方法——有什么好处?什么时候使用?
【发布时间】:2013-11-24 21:55:18
【问题描述】:

(这可能重复了ASP.NET MVC4 Async controller - Why to use?的问题,但是关于webapi,我不同意那里的答案)

假设我有一个长时间运行的 SQL 请求。它的数据应该被序列化为 JSON 并发送到浏览器(作为 xhr 请求的响应)。示例代码:

public class DataController : ApiController
{
    public Task<Data> Get()
    {
        return LoadDataAsync(); // Load data asynchronously?
    }
}

当我执行 $.getJson('api/data', ...) 时实际发生了什么(请参阅此海报http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster.pdf):

  1. [IIS] 请求被 IIS 接受。
  2. [IIS] IIS 等待托管池 (http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx) 中的一个线程 [THREAD] 并开始在其中工作。
  3. [THREAD] Webapi 在该线程和其他类中创建新的 DataController 对象。
  4. [THREAD] 使用任务并行库在 [THREAD2] 中启动 sql 查询
  5. [THREAD] 返回托管池,准备进行其他处理
  6. [THREAD2] 与 sql 驱动程序一起工作,在数据准备好时读取数据并调用 [THREAD3] 来回复 xhr 请求
  7. [THREAD3] 发送响应。

如果有问题,请随时纠正我。

他们说,在上面的问题中,重点和好处是,[THREAD2] 不是来自托管池,但是 MSDN 文章(上面的链接)说

默认情况下,TaskTask&lt;TResult&gt; 等并行库类型使用线程池线程来运行任务。

所以我得出一个结论,所有三个线程都来自托管池。

此外,如果我使用同步方法,我仍然会保持我的服务器响应,只使用一个线程(来自宝贵的线程池)。

那么,从 1 个线程切换到 3 个线程的实际意义是什么?为什么不直接最大化线程池中的线程呢?

是否有任何使用异步控制器的明显有用的方法?

【问题讨论】:

    标签: asp.net-web-api task-parallel-library async-await asynccontroller


    【解决方案1】:

    我认为主要的误解在于async 任务的工作方式。我的博客上有一个 async intro 可能会有所帮助。

    特别是,async 方法返回的Task 不会运行任何代码。相反,它只是通知调用者该方法的结果的一种便捷方式。您引用的 MSDN 文档仅适用于实际运行代码的任务,例如 Task.Run

    顺便说一句,您引用的海报与线程无关。以下是 async 数据库请求中发生的情况(略微简化):

    1. 请求被 IIS 接受并传递给 ASP.NET。
    2. ASP.NET 采用其线程池线程之一并将其分配给该请求。
    3. WebApi 创建DataController 等。
    4. 控制器操作启动异步 SQL 查询。
    5. 请求线程返回线程池。现在没有线程处理请求。
    6. 当结果从 SQL 服务器到达时,线程池线程会读取响应。
    7. 该线程池线程通知请求它已准备好继续处理。
    8. 由于 ASP.NET 知道没有其他线程正在处理该请求,它只是将请求分配给同一个线程,以便它可以直接完成它。

    如果你想要一些概念验证代码,我有an old Gist,它人为地将 ASP.NET 线程池限制为内核数(这是它的最小设置),然后执行 N+1 同步和异步请求.该代码只是延迟一秒钟,而不是联系 SQL 服务器,但一般原理是相同的。

    【讨论】:

    • “当结果从SQL服务器到达时,一个线程池线程读取响应”——你能详细说明一下吗?结果从哪里来?谁从线程池调用线程?
    • 假设您的 SQL 连接使用 TCP/IP,结果以网络数据包的形式到达。这会触发中断,设备驱动程序会在其中读取数据包并将其传递给用户模式。 IOCP 机制通知线程池一个套接字读取完成,它会确保响应完成,解析它,然后通知请求它已准备好继续。 (这仍然稍微简化了)。
    • 我对“通知线程池......”AFAIK 特别感兴趣,当可以通知某些内容时——它要么等待通知,要么定期检查通知队列。我得出一个结论,有 1+ 个专用线程用于执行此操作。那是对的吗?我们是否应该将该线程视为“因为异步而浪费”的资源?
    • 并非如此,因为 IOCP 线程是共享的,仅使用非常短暂,即使您不执行异步 I/O 也存在。这就像终结器线程;你不能说 that 异步操作使用了一个“浪费”线程,就像你可以说有一个“浪费”线程用于等待可终结对象被垃圾回收一样.有一个终结器线程,但它是共享的,就像 IOCP 一样。 More info here.
    • 谢谢。我感兴趣的是:这是如何在异步 webapi 操作方法之前完成的,以便没有线程等待处理请求?
    【解决方案2】:

    异步操作的好处在于,当控制器等待 sql 查询完成时,没有为该请求分配线程,而如果您使用同步方法,则线程将被锁定在从该方法的开始到结束。当 SQL 服务器在做它的工作时,线程除了等待什么也不做。 如果您使用异步方法,则同一线程可以在 SQL Server 执行其操作时响应其他请求。

    我认为您的步骤在第 4 步是错误的,我认为它不会创建一个新线程来执行 SQL 查询。在 6 处没有创建新线程,它只是一个可用线程,用于从第一个线程停止的地方继续。 6 处的线程可能与启动异步操作相同。

    【讨论】:

    • 那么,我可以编写一个应用程序,它可以同时启动两个 sql 查询,打印它们的结果,并且任何时候都不会使用多个线程?我想看看这个答案的概念证明。链接或代码。
    【解决方案3】:

    我认为以下内容描述了异步控制器相对于同步控制器的明显优势。

    使用同步方法为高延迟提供服务的 Web 应用程序 线程池增长到 .NET 4.5 默认最大值的调用 5, 000 个线程将比一个线程多消耗大约 5 GB 的内存 应用程序能够使用异步服务相同的请求 方法,只有 50 个线程。当你在做异步工作时, 你并不总是使用线程。例如,当您制作一个 异步 Web 服务请求,ASP.NET 不会使用任何 异步方法调用和等待之间的线程。使用线程 具有高延迟的服务请求池可能会导致大内存 占用空间和服务器硬件利用率低。

    来自Using Asynchronous Methods in ASP.NET MVC 4

    【讨论】:

      【解决方案4】:

      异步的目的不是让应用程序多线程,而是让单线程应用程序继续执行不同的操作,而不是等待在不同线程或进程上执行的外部调用的响应。

      考虑一个桌面应用程序,它显示来自不同交易所的股票价格。应用程序需要进行几次 REST / http 调用,以从每个远程证券交易所服务器获取一些数据。

      单线程应用程序会进行第一次调用,等到它得到第一组价格后什么都不做,更新它的窗口,然后调用下一个外部股票价格服务器,再次等到它得到价格之前什么都不做,更新它的窗口……等等。

      我们可以让所有多线程并行启动请求并并行更新屏幕,但由于大部分时间都花在等待来自远程服务器的响应上,这似乎有点过头了。

      线程最好: 向第一个服务器发出请求,但不要等待答案留下一个标记,一个在价格到达时返回的地方,然后继续发出第二个请求,再次留下一个可以返回的地方的标记..等等。

      当所有请求都发出后,应用程序执行线程可以继续处理用户输入或任何需要的内容。

      现在,当收到来自其中一台服务器的响应时,可以指示线程从之前放置的标记继续并更新窗口。

      以上所有内容都可以用单线程的长手编码,但是多线程通常更容易。现在,当我们编写 async/await 时,离开标记并返回的过程由编译器完成。都是单线程的。

      这里有两个关键点:

      1) 多线程仍然会发生!我们对股票价格的请求的处理发生在不同的线程上(在不同的机器上)。如果我们进行数据库访问,情况也是如此。在等待计时器的示例中,计时器在不同的线程上运行。虽然我们的应用程序是单线程的,但执行点只是在外部线程执行时(以受控方式)跳跃

      2) 一旦应用程序需要异步操作来完成,我们就会失去异步执行的好处。考虑一个显示来自两个交易所的咖啡价格的应用程序,该应用程序可以启动请求并在单个线程上异步更新它的窗口,但现在如果应用程序还计算两个交易所之间的价格差异,它就必须等待异步调用完成。这是强加给我们的,因为异步方法(例如,我们可能编写调用股票价格交易所的方法)不会返回股票价格,而是返回一个任务,这可以被认为是返回标记的一种方式被设置,所以函数可以完成并返回股票价格。

      这意味着每个调用异步函数的函数都需要异步或者等待调用堆栈底部的“其他线程/进程/机器”调用完成,如果我们正在等待底部调用完成得很好,为什么还要使用异步呢?

      当编写 web api,IIS 或其他主机是桌面应用程序时,我们异步编写控制器方法,以便主机可以在我们的线程上执行其他方法来服务其他请求,同时我们的代码正在等待来自工作的响应不同的线程/进程/机器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-01-06
        • 2015-01-27
        • 2015-04-05
        • 1970-01-01
        • 1970-01-01
        • 2011-07-20
        相关资源
        最近更新 更多