在asp.net Web API中,如何异步调用长时间运行的阻塞操作?
我有一个web api控制器操作,需要进行一次相对(10秒,例如)长时间运行的数据库调用。这似乎是异步方法的候选者。我可以将长时间运行的任务卸载到新线程上,并解除阻止我的asp.net请求线程以处理其他一些请求,因此我的简化控制器操作将如下所示:
public async Task<IHttpActionResult> Get()
{
IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
return Ok(things);
}
我遇到过几个博客(例如this one),这些博客可能不是在asp.net中实现这一目标的最佳方式。作者建议使用Task.FromResult()
并同步进行数据库调用,但我不知道这有何帮助;我的请求线程仍然会被阻塞,等待DB调用返回。
答案 0 :(得分:6)
首先,考虑同步调用会发生什么:
public IHttpActionResult Get()
{
IEnumerable<Thing> things = DoLongDbCall();
return Ok(things);
}
请求进来,ASP.NET抓取线程池线程来处理请求。该线程调用Get
方法,该方法完成工作。在整个请求期间使用一个线程池线程。
现在,让我们来看看当前代码中发生的事情(使用Task.Run
):
public async Task<IHttpActionResult> Get()
{
IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
return Ok(things);
}
请求进来,ASP.NET抓取线程池线程来处理请求。该线程调用Get
方法,该方法然后抓取另一个线程池线程来完成工作并将原始线程池线程返回给线程池。在整个请求期间使用一个线程池线程(并且在很短的时间内使用两个线程池线程)。
因此,Task.Run
代码强制进行额外的线程转换而不提供任何好处(在服务器端使用async
的整个要点是释放线程)。这就是为什么我建议不要在ASP.NET上使用Task.Run
(或任何其他方式在线程池上运行)。
正确的异步解决方案是使用异步数据库调用:
public async Task<IHttpActionResult> Get()
{
IEnumerable<Thing> things = await DoLongDbCallAsync();
return Ok(things);
}
请求进来,ASP.NET抓取线程池线程来处理请求。该线程调用Get
方法,然后该方法启动异步操作并将线程池线程返回给线程池。稍后,当db调用完成时,ASP.NET会抓取线程池线程来完成请求。对于大多数请求,使用 no 线程池线程(在请求的开头和结尾使用一个线程池线程一小段时间。)
答案 1 :(得分:2)
我的请求线程仍将被阻止,等待DB调用返回。
这不完全正确:
您的请求仍将等待漫长的操作结束,但您的线程可以自由处理其他操作。
您必须记住,IIS的可用线程数量减少(特别是在非服务器系统下运行)并释放不需要的线程总是一件好事。
此外,如果您缺少线程,并且使用User | Date | Discount
Andrew | 2015-06-16 13:13:00 | 30
Andrew | 2015-06-16 14:15:00 | 25
,则异步操作将等待可用线程,如果您不释放当前线程,则最终会得到一个可怕的僵局。