我们有许多使用await
/ async
关键字的异步控制器和服务。
许多动作看起来有点像:
public async Task<SomeViewModel> Get(int id)
{
var someData = await _service.GetData(id);
var someOtherData = await _service.GetMoreData(id);
return new SomeViewModel
{
Data = someData,
OtherData = someOtherData,
}
}
可能是服务调用本身也有多个await
。 await
通常会针对实体框架,服务总线或第三方Web端点的async
调用。
我的一位同事今天提出这样的代码是毫无意义的,它只会为线程管理生成额外的工作,而在负载下,我们实际上会为运行时生成更多的工作并减慢应用程序的速度结果。
它们是否正确?如果是,那么当您在Web API请求中有多个IO绑定调用时,async
/ await
的最佳做法是什么?
答案 0 :(得分:10)
我的一位同事今天提出这样的代码是毫无意义的,它只会为线程管理生成额外的工作,而在负载下,我们实际上会为运行时生成更多的工作并减慢应用程序的速度结果。
这很有意思,因为事实恰恰相反。正如其他回答者所指出的那样,如果您使用的是真正的异步操作(即,不是Task.Run
或类似的东西),那么使用更少的线程并且应用程序响应在负载下更好。
有些人(不是我)已经对转换到async
的“普通”ASP.NET应用程序进行了研究,他们发现在转移到async
而不是阻塞时,可扩展性提高了10倍到100倍调用。如果您的应用程序有更多异步工作要做,您可以期望更好的可伸缩性。
如果您查看单个请求,并且每个操作一次完成一个,则异步版本稍微更慢。但是如果你把系统视为一个整体 - 特别是在负载下 - 异步版本可以更好地扩展。异步处理程序的另一个方面经常被忽略的是,异步版本对突发加载的响应比线程池本身的响应更快。
此外,异步代码可以轻松执行并发请求,这也可以使个别请求更快:
public async Task<SomeViewModel> Get(int id)
{
var someDataTask = _service.GetData(id);
var someOtherDataTask = _service.GetMoreData(id);
await Task.WhenAll(someDataTask, someOtherDataTask);
return new SomeViewModel
{
Data = await someDataTask,
OtherData = await someOtherDataTask,
}
}
答案 1 :(得分:2)
如果您在应用池中一次只提供一个请求,阻止解决方案将更有效。
如果你几乎有任何并行性,async / await可能会更高效,因为它会减少线程,减少上下文切换。因此,I / O绑定的工作负载(如果阻塞,很可能是上下文切换)实际上是async / await发挥最佳效果的地方之一。
正如boklucius所回答的,.NET中的异步I / O目标是I / O完成端口,它使用线程池不阻塞I / O但是处理I / O 完成。使用async肯定不会破坏你的线程数。
答案 2 :(得分:1)
您可以查看Async Performance: Understanding the Costs of Async and Await by Stephen Toub。
I / O完成端口而不是多线程,切换影响应该可以忽略不计。一如既往,它取决于具体情况,如果有疑问的话。
我自己的经验表明,开销并不大,代码简单性也是值得的(我们的用例是一个tcp服务器,用于具有多个同时慢速客户端的自定义模糊协议)。它是对C ++ / threads / COM / win32应用程序的重写,使用大量await / async的.net版本在1/5行代码中的吞吐量是3倍,但正如我所说的那样。