具有 Task.Run 性能的 ASP.NET Web API 2 异步操作方法
Posted
技术标签:
【中文标题】具有 Task.Run 性能的 ASP.NET Web API 2 异步操作方法【英文标题】:ASP.NET Web API 2 Async action methods with Task.Run performance 【发布时间】:2015-05-23 23:02:47 【问题描述】:我正在尝试对几个 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));
看完Steve Sanderson's presentation后,我向每个端点发出了以下命令ab -n 100 -c 10 http://localhost....
。
我很惊讶,因为每个端点的基准似乎大致相同。
按照 Steve 的解释,我期待异步端点的性能更高,因为它会立即将线程池线程释放回线程池,从而使它们可用于其他请求并提高吞吐量。但数字似乎完全一样。
我在这里误会了什么?
【问题讨论】:
【参考方案1】:使用 await Task.Run
创建 "async" WebApi 是个坏主意 - 您仍将使用线程,甚至来自 the same thread pool used for requests。
这会导致一些不愉快的时刻,详细描述here:
额外的(不必要的)线程切换到 Task.Run 线程池线程。同样,当该线程完成请求时,它必须 输入请求上下文(这不是实际的线程切换,而是 确实有开销)。 创建了额外的(不必要的)垃圾。异步编程是一种折衷:以更高的响应速度为代价获得更高的响应能力 内存使用情况。在这种情况下,您最终会为 完全没有必要的异步操作。 Task.Run “意外”借用了一个线程池线程,导致 ASP.NET 线程池启发式算法失效。我没有很多 这里有经验,但我的直觉告诉我,启发式 如果意外的任务真的很短并且会恢复得很好 如果意外任务持续超过两次,就不会优雅地处理它 秒。 ASP.NET 无法提前终止请求,即,如果客户端断开连接或请求超时。在同步情况下, ASP.NET 知道请求线程并且可以中止它。在里面 异步情况下,ASP.NET 不知道辅助线程池 线程是“为”该请求的。可以通过使用来解决此问题 取消令牌,但这超出了本博文的范围。
基本上,您不允许对 ASP.NET 进行任何异步 - 您只需将 CPU 绑定的同步代码隐藏在 async 外观后面。 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。
【讨论】:
好的,我知道 Task.Run 在 ASP.NET 中是个坏主意,因为它使用线程池中的线程。我想我现在的问题是(1)我应该尝试将这项工作交给另一个线程吗?我对_newsFeedService.GetNewsFeedItemsForUser(userId)
的调用是对数据库的调用,我猜这是网络和I/O,而不是CPU 限制。 (2) 如果是个好主意,那段代码应该怎么写?我看到的所有示例都没有显示那部分代码。任何示例都会非常有用。
@SimonLomax 只需搜索“C# entity framework async” - msdn.microsoft.com/en-us/data/jj819165.aspx 如果您使用实体框架或“C# database async” -tugberkugurlu.com/archive/… 如果您使用标准 ADO。
我实际上使用的是 mongo 和 mongo 的 c# 驱动程序,而不是实体框架。无论如何,根据您的说法,除非我从自定义方法 _newsFeedService.GetNewsFeedItemsForUser(userId);
进行的调用是异步的,否则尝试使我的控制器操作方法异步是没有意义的。
@SimonLomax 似乎 MongoDB 有一些异步 API ***.com/questions/19740329/mongodb-net-async-await。但是您显然必须更改 GetNewsFeedItemsForUser
方法才能通过此 API 工作。它可能会成为一个不平凡但几乎不是很难的改变。以上是关于具有 Task.Run 性能的 ASP.NET Web API 2 异步操作方法的主要内容,如果未能解决你的问题,请参考以下文章
Asp.net core web Api Fire and Forget BG task