【问题标题】:Are there any benefits of using a task if we synchronously wait for the result?如果我们同步等待结果,使用任务有什么好处吗?
【发布时间】:2014-03-21 23:38:44
【问题描述】:

我有一个需要调用从数据库调用返回任务的异步方法的部分操作。由于我无法进行异步部分操作,我需要等待任务完成:

public ActionResult TopMenu()
{
  var topMenuTask = Task.Run<IEnumerable<TopMenuItem>>( () => 
    { return _menuService.GetTopMenu(); });
  topMenuTask.Wait();

  //view model population code goes here

  return PartialView(viewModel);
}

据我了解,执行异步 DB (IO) 调用的好处是我们将线程重新排队到线程池中,这样它就可以在 IO 完成时处理更多请求,但这不是一个有趣的点这种情况是我们同步等待的吗?

【问题讨论】:

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


    【解决方案1】:

    我从您的问题描述中假设GetTopMenu 返回Task,因此返回should actually be called GetTopMenuAsync

    在这种情况下,您最好的选择可能是您已经在做的事情:

    public ActionResult TopMenu()
    {
      var topMenuTask = Task.Run(() => _menuService.GetTopMenuAsync());
      var topMenu = topMenuTask.Result;
    
      //view model population code goes here
    
      return PartialView(viewModel);
    }
    

    据我了解,执行异步 DB (IO) 调用的好处是我们将线程重新排队到线程池中,这样它就可以在 IO 完成时处理更多请求,但这不是一个有趣的点这种情况是我们同步等待的吗?

    正确。通过将(异步)数据库访问包装在(同步)部分操作中,代码将抵消async 的好处。

    这是平台的限制 (ASP.NET MVC)。请投票on the issueon uservoice

    【讨论】:

    • 根据@Noseratio,我们不应该在 ASP.NET 中运行 Task.Run。你同意吗?
    • 是的,这是一个很好的一般规则。如果您有 同步 GetTopMenu,那么您应该使用它。我的代码示例假设您只有有一个可用的异步GetTopMenuAsync,并且您必须在子操作中使用它。
    • Stephen,您是否建议在此处保留Task.Run 以避免潜在的死锁情况?我认为@JonasStawski 可能想先尝试直接致电_menuService.GetTopMenu[Async]().Result,看看是否可行。如果在其中的调用链中的任何位置都没有await,则应该这样做。
    • @Noseratio:我可以选择任何一种方式。 Task.Run 的好处是,如果将来代码发生变化,它会更有弹性;但Result 效率更高。
    【解决方案2】:

    您将从线程池中调用一个线程来执行该逻辑,但 Web 请求线程将被阻塞以等待结果。所以不,没有任何好处。

    你为什么不能这样做?:

    public async Task<ActionResult> TopMenu()
    {
        var topMenu = await Task.Run<IEnumerable<TopMenuItem>>( () => { return _menuService.GetTopMenu(); });
    
        //view model population code goes here
    
        return PartialView(viewModel);
    }
    

    另一种选择是使用经典的异步 MVC 方式,但您必须从 AsyncController 而不是 Controller 派生控制器:

      public void TopMenuAsync()
      {
          AsyncManager.OutstandingOperations.Increment();
          Task.Run<IEnumerable<TopMenuItem>>(() => { return _menuService.GetTopMenu(); })
              .ContinueWith(t =>
              {
                  AsyncManager.Parameters["topMenu"] = t.Result;
                  AsyncManager.OutstandingOperations.Decrement();
              });
      }
    
      public ActionResult TopMenuCompleted(TopMenuItem topMenu)
      {
          //view model population code goes here
    
          return PartialView(viewModel);
      }
    

    【讨论】:

    • 来自问题:“我不能有异步部分操作”。我认为这只是 ASP.NET MVC 的一个限制。
    • 我的说法不正确吗?我尝试在操作上使用异步,但由于问题中提到的原因而失败。使用 Html.RenderPartial 调用部分操作
    • 我试过@Html.Partial,效果很好。稍后我将尝试使用@Html.RenderPartial
    • @vtortola,你是说 Razor 中的 Html.Partial 与异步 Task&lt;ActionResult&gt; 部分操作一起工作,真的吗?你也试过@Html.RenderPartial 吗?
    【解决方案3】:

    您不应该首先在 ASP.NET 中使用Task.Run。您仍然受到单个 HTTP 请求/响应时间框架的限制,它不会加快内容交付,只会引入额外的线程切换,请查看 this

    因此,您的操作应该如下所示:

    public ActionResult TopMenu()
    {
      var topMenu = return _menuService.GetTopMenu();
    
      //view model population code goes here
    
      return PartialView(viewModel);
    }
    

    如果您的局部视图可能需要一段时间才能呈现,请考虑使用如下技术:

    http://blog.michaelckennedy.net/2012/11/13/improve-perceived-performance-of-asp-net-mvc-websites-with-async-partialviews/

    【讨论】:

    • 我的语法有错误,特别是return _menuService.GetTopMenu()
    • @JonasStawski,请参考@StephenCleary 的回答,他解释得比我做得好。如果GetTopMenu 返回一个Task,看看里面是否还有另一个Task.Run 包装器,如果你可以访问源代码,就去掉它。
    【解决方案4】:

    您是正确的.. 这通常是您将使用 Task 的决定推迟到调用者的原因,或者提供异步和非异步版本的调用(例如:GetTopMenu 和 @987654323 @)。如果你强制使用异步调用,那么你就会遇到这样的情况。

    您的替代方法是加载视图并通过 AJAX 异步进行调用。在这种情况下,您还可以给用户视觉反馈。在这种情况下使用await 将释放工作进程以供 IIS 继续处理请求。

    【讨论】:

      猜你喜欢
      • 2020-10-24
      • 1970-01-01
      • 2012-09-11
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-16
      相关资源
      最近更新 更多