使控制器异步的最简单方法

时间:2014-06-07 01:25:06

标签: c# .net asp.net-mvc-5 async-await

我继承了一个使用MVC5和C#的大型Web应用程序。我们的一些控制器进行了几次慢速数据库调用,我想让它们异步,以便允许工作线程在等待数据库调用完成时为其他请求提供服务。我想用最少量的重构来做到这一点。说我有以下控制器

public string JsonData()
{
   var a = this.servicelayer.getA();
   var b = this.servicelayer.getB();
   return SerializeObject(new {a, b});
}

我通过保持服务层不变并将控制器重写为

,使两个昂贵的调用a,b异步
public async Task<string> JsonData()
{
   var task1 = Task<something>.Run(() => this.servicelayer.getA());
   var task2 = Task<somethingelse>.Run(() => this.servicelayer.getB());
   await Task.WhenAll(task1, task2);
   var a = await task1;
   var b = await task2;
   return SerializeObject(new {a, b});
}

上面的代码运行没有任何问题,但我不能告诉使用Visual Studio,如果工作线程现在可以为其他请求提供服务,或者如果在asp.net控制器中使用Task.Run()则不会。做我认为它做的事情。任何人都可以评论我的代码的正确性,以及它是否可以以任何方式改进?另外,我读到在控制器中使用async会产生额外的开销,应该只用于长时间运行的代码。我可以用来确定控制器是否需要异步的最低标准是什么?我知道每个用例都不同,但想知道是否有一个基线我可以用作起点。 2个数据库通话?什么超过2秒返回?

2 个答案:

答案 0 :(得分:6)

指南是,只要有I / O,就应该使用异步。即,一个数据库。与任何类型的I / O相比,开销微乎其微。

也就是说,通过Task.Run阻塞线程池线程就是我所谓的“假异步”。这正是你 想要在ASP.NET上做的事情。

相反,从“最低级别”代码开始,使其真正异步。例如,EF6支持异步数据库查询。然后让async代码从那里自然地增长到你的控制器。

答案 1 :(得分:2)

新代码的唯一改进是它同时运行A和B,而不是一次运行一个。在这段代码中实际上没有真正的异步。

当你使用Task.Run时,你正在卸载要在另一个线程上完成的工作,所以基本上你在等待两个任务时启动2个线程并释放当前线程(每个线程完全同步运行)

这意味着操作将更快完成(因为并行性),但将使用两次线程,因此可扩展性较差。


想要做的是确保您的所有操作都是真正异步的。这意味着拥有servicelayer.getAAsync()servicelayer.getBAsync(),这样您就可以在处理IO时真正释放线程:

public async Task<string> JsonData()
{
    return SerializeObject(new {await servicelayer.getAAsync(), await servicelayer.getBAsync()});
}

如果您无法确保实际的IO操作真的是异步,那么保留旧代码会更好。

有关避免Task.RunTask.Run Etiquette Examples: Don't Use Task.Run in the Implementation

的原因的详情