C# ASP.NET Web API 2具有Task.Run性能的异步操作方法

C# ASP.NET Web API 2具有Task.Run性能的异步操作方法,c#,asp.net,asp.net-web-api,task-parallel-library,async-await,C#,Asp.net,Asp.net Web Api,Task Parallel Library,Async Await,我正在尝试(使用Apache bench)对两个ASP.NET Web API 2.0端点进行基准测试。其中一个是同步的,另一个是异步的 [Route("user/{userId}/feeds")] [HttpGet] public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId) { return _newsFeed

我正在尝试(使用Apache bench)对两个ASP.NET Web API 2.0端点进行基准测试。其中一个是同步的,另一个是异步的

        [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));
        }
[路由(“用户/{userId}/feeds”)]
[HttpGet]
public IEnumerable GetNewsFeedItemsForUser(字符串用户ID)
{
return\u newsFeedService.GetNewsFeedItemsForUser(userId);
}
[路由(“用户/{userId}/feeds/async”)]
[HttpGet]
公共异步任务GetNewsFeedItemsForUserAsync(字符串用户ID)
{
返回等待任务。运行(()=>_newsFeedService.GetNewsFeedItemsForUser(userId));
}
在观看之后,我发出了以下命令
ab-n100-c10http://localhost....
到每个端点

我很惊讶,因为每个端点的基准似乎都差不多

按照Steve的解释,我希望异步端点的性能会更好,因为它会立即将线程池线程释放回线程池,从而使它们可用于其他请求并提高吞吐量。但数字似乎完全相同


我在这里误解了什么?

使用
等待任务。运行
创建“异步”WebApi是个坏主意-您仍然会使用线程,甚至从

它会导致一些不愉快的时刻,详细描述如下:

  • 额外(不必要)线程切换到任务。运行线程池线程。类似地,当该线程完成请求时,它必须 输入请求上下文(不是实际的线程开关,而是 确实有开销)
  • 创建了额外(不必要)的垃圾。异步编程是一种折衷:您可以以更高的响应速度为代价获得更高的响应速度 内存使用。在这种情况下,您最终会为 完全不必要的异步操作
  • ASP.NET线程池启发被任务抛出。请“意外”借用线程池线程运行。我没有很多钱 这里的经验,但我的直觉告诉我 如果意外任务确实很短,并且 如果意外任务持续两个月以上,则不要优雅地处理 秒
  • ASP.NET无法提前终止请求,即如果客户端断开连接或请求超时。在同步情况下, ASP.NET知道请求线程,因此可以中止它。在 异步情况下,ASP.NET不知道辅助线程池 线程是该请求的“for”。可以通过使用 取消令牌,但这超出了本文的范围
基本上,您不允许对ASP.NET进行任何异步—您只需将绑定CPU的同步代码隐藏在异步外观后面<代码>异步
本身是I/O绑定代码的理想选择,因为它允许以最高效率利用CPU(线程)(I/O无阻塞),但当您有计算绑定代码时,您仍必须以相同的程度利用CPU

考虑到
任务
和上下文切换带来的额外开销,您将获得比简单同步控制器方法更糟糕的结果

如何使其真正异步:

GetNewsFeedItemsForUser
方法应转换为
async

    [Route("user/{userId}/feeds/async")]
    [HttpGet]
    public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
    {
        return await _newsFeedService.GetNewsFeedItemsForUser(userId);
    }
[Route(“user/{userId}/feeds/async”)]
[HttpGet]
公共异步任务GetNewsFeedItemsForUserAsync(字符串用户ID)
{
return wait_newsFeedService.GetNewsFeedItemsForUser(userId);
}
要做到这一点:

  • 如果是某个库方法,则查找其
    async
    变体(如果没有-运气不好,您将不得不搜索一些竞争对手)
  • 如果是使用文件系统或数据库的自定义方法,则利用其异步功能为该方法创建异步API

好的,我明白了。在ASP.NET中运行是个坏主意,因为它使用线程池中的线程。我想我现在的问题是(1)我应该把这项工作交给另一个线程吗?我对
\u newsFeedService.GetNewsFeedItemsForUser(userId)
的调用是对数据库的调用,我猜它是网络和I/O,而不是CPU绑定的。(2) 如果这是一个好主意,那么应该如何编写代码?我看到的所有示例都没有显示这一部分代码。任何示例都会非常有用。@SimonLomax只要搜索“C#entity framework async”-如果您使用实体框架或“C#database async”-如果您使用标准ADO。我实际上使用的是mongo和mongo的C#驱动程序,而不是实体框架。不管怎样,根据您的说法,除非调用我的自定义方法
\u newsFeedService.GetNewsFeedItemsForUser(userId)是异步的,那么尝试使我的控制器操作方法异步就没有意义了。@SimonLomax MongoDB似乎有一些异步API。但是您显然必须更改
GetNewsFeedItemsForUser
方法才能使用此API。这可能会成为一个不平凡但并不十分困难的变化。