我正在尝试对几个 ASP.NET Web API 2.0 端点进行基准测试(使用 Apache bench)。其中一种是同步的,一种是异步的。
[Route("user/{userId}/feeds")]
[HttpGet]
public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId)
{
return _newsFeedService.GetNewsFeedItemsForUser(userId);
}
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await Task.Run(() => _newsFeedService.GetNewsFeedItemsForUser(userId));
}
看完后史蒂夫·桑德森的演讲我发出了以下命令ab -n 100 -c 10 http://localhost....
到每个端点。
我很惊讶,因为每个端点的基准似乎大致相同。
根据 Steve 的解释,我期望异步端点的性能会更高,因为它会立即将线程池线程释放回线程池,从而使它们可用于其他请求并提高吞吐量。但数字看起来完全一样。
我在这里误解了什么?
Using await Task.Run
创造"async"WebApi 是一个坏主意 - 你仍然会使用线程,甚至从用于请求的相同线程池.
它将导致一些不愉快的时刻,详细描述here:
- 额外的(不必要的)线程切换到Task.Run线程池线程。类似地,当该线程完成请求时,它必须
进入请求上下文(这不是实际的线程切换,而是
确实有开销)。
- 会产生额外的(不必要的)垃圾。异步编程是一种权衡:您获得更高的响应能力,但代价是更高的性能
内存使用情况。在这种情况下,您最终会为
完全没有必要的异步操作。
- ASP.NET 线程池启发法因 Task.Run“意外”借用线程池线程而失效。我没有很多
在这里的经验,但我的直觉告诉我启发式
如果意外任务真的很短并且会恢复得很好
如果意外任务持续超过两个时间,就不能优雅地处理它
秒。
- ASP.NET 无法提前终止请求,即,如果客户端断开连接或请求超时。在同步情况下,
ASP.NET 知道请求线程并可以中止它。在里面
异步情况下,ASP.NET 不知道辅助线程池
线程是“用于”该请求的。可以通过使用来解决这个问题
取消令牌,但这超出了本博客文章的范围。
基本上,您不允许 ASP.NET 出现任何异步 - 您只需将受 CPU 限制的同步代码隐藏在异步外观后面。Async
它本身对于 I/O 密集型代码来说是理想的,因为它允许以最高效率利用 CPU(线程)(I/O 不会阻塞),但是当您有计算密集型代码时,您仍然必须利用 CPU到同样的程度。
并考虑到额外的开销Task
和上下文切换你会得到比简单的同步控制器方法更糟糕的结果。
如何使其真正异步:
GetNewsFeedItemsForUser
方法应改为async
.
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await _newsFeedService.GetNewsFeedItemsForUser(userId);
}
去做吧:
- 如果它是某个库方法,则查找它的
async
变体(如果没有 - 运气不好,你将不得不寻找一些竞争的类似物)。
- 如果它是使用文件系统或数据库的自定义方法,则利用它们的异步设施为该方法创建异步 API。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)