ApiController的异步方法 - 有什么好处?什么时候用?

Rus*_*fin 25 task-parallel-library asynccontroller async-await asp.net-web-api

(这可能与ASP.NET MVC4异步控制器问题重复- 为什么要使用?,但关于webapi,我不同意那里的答案)

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

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

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

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

如果出现问题,请随时纠正我.

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

默认情况下,并行库类型类似TaskTask<TResult>使用线程池线程来运行任务.

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

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

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

有没有明显有用的方法来使用异步控制器?

Ste*_*ary 29

我认为关键的误解是关于async任务如何运作.我在我的博客上有一个可能有帮助的async介绍.

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

顺便说一下,你引用的海报与线程无关.这是async数据库请求中发生的事情(略微简化):

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

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

  • 假设您的SQL连接使用TCP/IP,结果将作为网络数据包到达.这会触发中断,设备驱动程序会读取数据包并将其传递给用户模式.IOCP机制通知线程池套接字读取完成,它将确保响应完成,解析它,然后通知请求它已准备好继续.(这仍然*略微简化). (2认同)
  • 不是真的,因为IOCP线程是共享的,只是非常简单地使用,即使你不进行异步I/O也存在.这就像终结者线程; 你不能真的说**异步操作使用了一个"浪费"的线程,不过你可以说有一个"浪费"的线程用于等待一个可终结的对象被垃圾收集.有一个终结器线程,但它是共享的,就像IOCP一样.[更多信息在这里](http://msdn.microsoft.com/en-us/library/aa365198%28VS.85%29.aspx). (2认同)