【问题标题】:Proper use of Task.Run & Task.WaitAll for EF calls in ASP.NET在 ASP.NET 中正确使用 Task.Run 和 Task.WaitAll 进行 EF 调用
【发布时间】:2020-09-16 05:14:25
【问题描述】:

简短版:

任何人都可以确认,带有 EF 调用的非异步子函数上的 Task.Run/Task.WaitAll 对 ASP.NET 应用程序有什么好处吗?

长版:

我们有一个 Asp.Net WebApi 服务,该服务具有一个方法,该方法进行多个 DB 调用(全部使用 EntityFramework)来收集一组数据,这些数据全部返回到单个响应对象中。我们将其分解为一系列子函数,并希望它们并行运行(注意:这些函数都不使用任何异步调用)。

为了实现某种形式的并行编码,我们使用 Task.Run 调用每个函数,然后是 Task.WaitAll:

public ResponseObject PopulateResponseObject(int id)
{
    var response = new ResponseObject();

    Task<DataSetA> dataSetATask = Task.Run(() => getDataSetA(id));
    Task<DataSetB> dataSetBTask = Task.Run(() => getDataSetB(id));
    Task.WaitAll(dataSetATask, dataSetBTask);

    response.SetA = dataSetATask.Result;
    response.SetB = dataSetBTask.Result;

    return response;
}

从我一直在阅读的内容来看,Task.Run 在 Asp.Net 应用程序中可能不会获得太多收益,而我们这里的内容可能只会导致不必要的线程池开销。

我们这样写代码是不是在浪费时间?

更新: 我们熟悉 EF 具有异步版本的事实,但是需要修改大量代码。我们希望保持这些功能不变。

【问题讨论】:

  • 您可以只使用Parallel.ForEach 重载之一来节省一些麻烦
  • 这取决于使用模式。如果你有一个相对很少调用的端点,你可能会提高性能(尤其是在一些长时间运行的东西的情况下),但总的来说 seems 不值得麻烦。
  • 这将使用比需要更多的线程。创建async-await 是为了节省线程使用量。 EF 有异步 API。
  • @Marie - 好建议。我刚刚实现了一个 Parallel.Invoke 版本并正在对其进行测试。
  • 当系统处于负载状态时,无法判断并行运行这些是否会产生任何好处。如果您正在处理来自许多用户的请求,线程很容易成为稀缺资源,您最终可能会等待一个可用。由于并发锁,您可能还会遇到线程之间的阻塞。判断是否有整体效益的唯一方法是模拟预期负载并查看系统如何响应。另请参阅race your horses

标签: c# asp.net async-await task


【解决方案1】:

使用 Task.Run 至少可以让您并行运行两个查询,所以是的,有待改进(与您按顺序运行查询的完全同步版本相比)。

但是,收益受限于您在线程池中拥有的线程数(最终受限于您的服务器容量)。通过使用 EF 异步 api,您将获得更多收益,因为这将允许执行许多查询,而无需为每个查询占用一个线程。

但这是开始使用异步代码的合理策略。完成此操作后,您可以返回并慢慢将查询转换为使用异步 api。

【讨论】:

    【解决方案2】:

    根据我的经验,我强烈建议您永远不要为了微不足道的性能提升而放弃简单性。这种并行性的影响在我看来似乎微不足道,因为开发、维护和调试负担很大。

    特别是因为您使用 EF 并与 DB 交互,您可能会面临许多可能是母马的并发问题!我也建议看看this

    【讨论】:

      【解决方案3】:

      在对 Sync、Async(使用上述方法)和 Parallel.Invoke 进行了一些性能测试后,我发现了一些有趣的结果。并行版本类似于:

      public ResponseObject PopulateResponseObject(int id)
      {
          var response = new ResponseObject();
      
          Parallel.Invoke(
              () => response.SetA = getDataSetA(id),
              () => response.SetB = getDataSetB(id),
              );
      
          return response;
      }
      

      Async 出现在上面,在我原来的帖子中。

      结果表明,与基本的 Sync 调用相比,Async 调用 (Task.Run/Task.WaitAll) 的平均运行时间为 57%,而 Parallel.Invoke 的运行时间为 65%。

      因此,在 ASP.NET 应用程序中使用 Task.Run/WaitAll 有一个优势。

      更新:顺便说一句,这个测试是通过将我们的代码推送到我们的 QA 人员正在执行测试的测试环境来完成的。它当然不会模仿我们的 prod 环境,但确实表明它稍微好一些。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-07
        • 1970-01-01
        • 1970-01-01
        • 2016-09-16
        相关资源
        最近更新 更多