在 webapi 中使用异步等待的最佳实践

Posted

技术标签:

【中文标题】在 webapi 中使用异步等待的最佳实践【英文标题】:best practice for using async await in webapi 【发布时间】:2017-07-05 16:39:28 【问题描述】:

我有 .NET 核心 Web API 作为服务层。服务层有所有的 EF 代码。

如果有带有此代码的基本控制器

protected Task<IActionResult> NewTask(Func<IActionResult> callback)

    return Task.Factory.StartNew(() =>
    
        try
        
            return callback();
        
        catch (Exception ex)
        
            Logger.LogError(ex.ToString());
            throw;
        
    );

在控制器操作中,我将所有对服务的调用包装在上述方法中,例如:

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)

    return await NewTask(() =>
    
        var result = _somethingService.GetSomething(somethingId);

        if (result != null)
            return Ok(result);
        else
            return NotFound("Role not found");
    );

考虑到明天我可能有多个服务调用或调用其他 Web 服务,这是正确的模式吗?请指教。

【问题讨论】:

您能否提供更多关于您在此处尝试完成的任务的信息?您的代码现在基本上通过在后台线程上执行同步工作来伪造异步工作。更不用说使用 Task.Factory.StartNew 非常危险,您应该使用 Task.Run 代替,但您甚至不需要 Task.Run 在这里。 某项服务有一些使用实体框架核心的 crud 操作。明天我也可能有一个名为 feedService 的服务,它将调用使用 httpclient 从外部 Web 获取提要。我希望我的 api 从异步等待中受益。上述模式是否可以满足这些需求 + 任何问题或改进。? 【参考方案1】:

我希望我的 api 从异步等待中受益。上述模式将满足这些需求

不,它没有。在线程池上运行同步工作给您带来了同步异步代码的缺点,而两者都没有。

某项服务有一些使用实体框架核心的 crud 操作

目前,您的操作方法就是我所说的“假异步”——它看起来是异步的(例如,使用 await),但实际上只是在后台线程上运行阻塞代码。在 ASP.NET 上,您需要真正的异步,这意味着您必须始终保持异步。有关为什么这在 ASP.NET 上不好的更多信息,请参阅我的 intro to async on ASP.NET article 的前半部分(它主要处理 ASP.NET 非核心,但第一部分讨论同步与异步请求对任何类型的服务器都有效)。

要实现真正的异步,您应该从最低级别开始 - 在这种情况下,您的 EFCore 调用。它们都支持异步。因此,请将 API 调用(如 x.FirstOrDefault())替换为 await x.FirstOrDefaultAsync()(所有创建/更新/删除等都相同)。

然后让async/await从那里自然生长;编译器会指导你。您最终会在 somethingService 上使用异步方法,这些方法可以这样使用:

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)

  var result = await _somethingService.GetSomethingAsync(somethingId);
  if (result != null)
    return Ok(result);
  else
    return NotFound("Role not found");

【讨论】:

ef 核心异步调用比同步调用慢,请告知。 @user1603828:差异应该可以忽略不计。如果这不是您所看到的,我建议您联系 EF 团队。 EF 6 异步和同步调用之间的差异可以忽略不计,但是 EF Core 中异步和同步调用之间的差异相当大。我相信他们会在不久的将来解决这个问题,但我的建议是考虑你是否真的那么关心性能并使用异步调用。 here 你可以看到一些关于 EF Core 中异步和同步调用之间差异有多大的测试。 @krishna 我将再次发布该播客的链接,以便在此处阅读您评论的任何人都可以看到它。性能和吞吐量之间存在差异,Kathleen 描述了异步应该如何在性能上最多增加 10% 的开销,但在吞吐量上的性能更显着。 dotnetrocks.com/?show=1433【参考方案2】:

好的,首先,您应该停止使用Task.Factory.StartNew,而仅当您需要在线程池线程上运行繁重的CPU 密集型工作时才使用Task.Run。在你的情况下,你根本不需要那个。另外你应该记住,你应该只在调用方法时使用Task.Run,而不是在方法的实现中使用。你可以阅读更多关于here的信息。

当您实际调用数据库并且想要使用异步时,您真正想要的是在您的服务中进行异步工作(我不确定您是否甚至需要在您的情况下使用服务) /await 而不仅仅是在后台线程上运行一些东西。

基本上你的服务应该是这样的(如果你确定你需要一个服务):

class PeopleService

    public async Task<Person> GetPersonByIdAsync(int id)
    
        Person randomPerson = await DataContext.People.FirstOrDefaultAsync(x => x.Id == id);
        return randomPerson;
    

如您所见,您的服务现在对数据库进行异步调用,这基本上就是您的模式应该是的。您可以将此应用于所有操作(添加/删除/等)

使您的服务异步后,您应该能够轻松地使用操作中的数据。

您的操作应如下所示:

[HttpGet("something")]
public async Task<IActionResult> GetPerson(int id)

    var result = await PeopleService.GetPersonByIdAsync(id);

    if (result != null)
        return Ok(result);
    else
        return NotFound("Role not found");

【讨论】:

我对 ef 核心异步方法做了一些研究,它说它们比同步 ef 方法慢.. 请告知。 @kirshna 异步方法速度较慢。当同步运行它们时,它们会产生不可避免的开销。但它们可以增加吞吐量。因此,您应该考虑吞吐量和性能之间的平衡。与性能问题一样,它很复杂。 dotnetrocks.com/?show=1433 听一听,你就会明白我在说什么。

以上是关于在 webapi 中使用异步等待的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

服务之间异步通信的最佳实践

何时使用异步和等待 - 客户端与 webapi [关闭]

在 ASP.NET WebApi 中路由相关实体的最佳实践是啥

RESTful API 设计最佳实践

构建异步邮件的最佳实践(使用 Sidekiq)

在 Web API/OWIN 中使用大量声明的最佳实践