Web Api中的异步调用

ric*_*lla 2 c# asynchronous asp.net-web-api

在asp.net Web API中,如何异步调用长时间运行的阻止动作?

我有一个Web api控制器操作,需要进行一个相对较长(例如10秒)的长时间运行的数据库调用。这似乎是异步方法的候选对象。我可以将长时间运行的任务卸载到新线程上,并解除阻塞asp.net请求线程以处理其他一些请求,因此简化的控制器动作如下所示:

public async Task<IHttpActionResult> Get()
{
    IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
    return Ok(things);
}
Run Code Online (Sandbox Code Playgroud)

我曾经遇到过一对夫妇的博客(这一个,例如)暗示这可能不是最佳的方式在asp.net来实现这一目标。作者建议Task.FromResult()同步使用和执行DB调用,但是我看不到这有什么帮助;我的请求线程仍将被阻塞,等待DB调用返回。

Ste*_*ary 5

首先,考虑一下同步调用会发生什么:

public IHttpActionResult Get()
{
  IEnumerable<Thing> things = DoLongDbCall();
  return Ok(things);
}
Run Code Online (Sandbox Code Playgroud)

一个请求进入,ASP.NET抓取一个线程池线程来处理该请求。该线程调用Get方法,该方法完成工作。在整个请求期间使用一个线程池线程。

现在,让我们来看一下当前代码中的情况(使用Task.Run):

public async Task<IHttpActionResult> Get()
{
  IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
  return Ok(things);
}
Run Code Online (Sandbox Code Playgroud)

一个请求进入,ASP.NET抓取一个线程池线程来处理该请求。该线程调用该Get方法,然后该方法获取另一个线程池线程来进行工作,然后将原始线程池线程返回给线程池。在整个请求期间使用一个线程池线程(在很短的时间内使用两个线程池线程)。

因此,Task.Run代码在不提供任何好处的情况下强制执行额外的线程转换(async在服务器端使用的全部目的是释放线程)。这就是为什么我建议不要Task.Run在ASP.NET上使用(或任何其他方式在线程池上运行工作)的原因。

适当的异步解决方案是使用异步DB调用:

public async Task<IHttpActionResult> Get()
{
  IEnumerable<Thing> things = await DoLongDbCallAsync();
  return Ok(things);
}
Run Code Online (Sandbox Code Playgroud)

一个请求进入,ASP.NET抓取一个线程池线程来处理该请求。该线程调用该Get方法,该方法然后启动异步操作,并将线程池线程返回到线程池。稍后,当db调用完成时,ASP.NET抓取线程池线程以完成请求。对于大多数请求,不使用线程池线程(在请求的开始和结束时短暂使用一个线程池线程)。