【问题标题】:Async calls never returns in Asp.Net MVC异步调用永远不会在 Asp.Net MVC 中返回
【发布时间】:2016-01-16 02:51:01
【问题描述】:

我返回一个列表,它基本上调用了两个异步操作:

[HttpPost]
public ActionResult List(DataSourceRequest command, ProductListModel model)
{
    var categories = _productService.GetAllProducts(model.SearchProductName,
        command.Page - 1, command.PageSize);

    var gridModel = new DataSourceResult
    {
        Data = categories.Select(async x =>
        {
            var productModel = x.ToModel();
            var manufacturer = await _manufacturerService.GetManufacturerById(x.ManufacturerId);
            var category = await _categoryService.GetCategoryById(x.CategoryId);

            productModel.Category = category.Name;
            productModel.Manufacturer = manufacturer.Name;
            return productModel;
        }),
        Total = categories.TotalCount
    };
    return Json(gridModel);
}

这是一个 ajax 请求(来自客户端),但在前端它永远不会返回。有没有死锁?

【问题讨论】:

  • awaiting 不在一个 (a) 未标记为 async 且 (b) 未返回 Task<ActionResult> 的方法内时,您的调用情况如何?
  • 因为它是一个 lamda 表达式。如果我将方法标记为async Task<ActionResult>,编译器会报告我的方法缺少await 运算符。就算ii放了也没用。
  • 酷,我刚刚看到了async 代表。从外观上看,这就是问题所在。 DataTask<T>,其中 Tx.ToModel() 类型吗?
  • 不,Data = IEnumberable 而 ToModel() 是 ViewModel,即 ProductModel()
  • 很确定它将是一个IEnumerable<Task>,因为异步委托传递给.Select()。这意味着它不会等待任务完成。如果你 await categories.Select() 通过包装在 Task.WhenAll() 中调用,将操作标记为 async 并返回 Task<ActionResult>,我相信这应该可以解决你的问题

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


【解决方案1】:

从几个 cmets 和@usr 的答案中建立我的答案:

  • 上面代码中的Data 实际上是IEnumerable<Task<ProductModel>>,而不是IEnumerable<ProductModel>。这是因为传递给 Select 的 lambda 是 async
  • 很可能,JSON 序列化程序正在遍历此结构并枚举 Task<ProductModel> 实例上的属性,包括 Result

在这种情况下,我会在我的博客why accessing Result will cause a deadlock 上进行解释。简而言之,这是因为async lambda 将尝试在其await 之后在ASP.NET 请求上下文上恢复执行。但是,ASP.NET 请求上下文在调用 Result 时被阻止,从而锁定该请求上下文中的线程,直到 Task<T> 完成。由于async lambda 无法恢复,它无法完成该任务。所以这两件事都在等待对方,你就会遇到典型的僵局。

有一些使用await Task.WhenAll 的建议,我通常会同意。但是,在这种情况下,您使用的是实体框架并收到此错误:

在前一个异步操作完成之前,在此上下文上启动了第二个操作。

这是因为 EF 无法在同一个数据库上下文中同时执行多个异步调用。有几种方法可以解决这个问题;一种是使用多个数据库上下文(基本上是多个连接)同时进行调用。 IMO 一种更简单的方法是按顺序而不是同时进行异步调用:

[HttpPost]
public async Task<ActionResult> List(DataSourceRequest command, ProductListModel model)
{
  var categories = _productService.GetAllProducts(model.SearchProductName,
      command.Page - 1, command.PageSize);

  var data = new List<ProductModel>();
  foreach (var x in categories)
  {
    var productModel = x.ToModel();
    var manufacturer = await _manufacturerService.GetManufacturerById(x.ManufacturerId);
    var category = await _categoryService.GetCategoryById(x.CategoryId);

    productModel.Category = category.Name;
    productModel.Manufacturer = manufacturer.Name;
    data.Add(productModel);
  }

  var gridModel = new DataSourceResult
  {
    Data = data,
    Total = categories.TotalCount
  };
  return Json(gridModel);
}

【讨论】:

  • Async LINQ 将是一个不错的选择。
  • 伟大的总结斯蒂芬 :)
【解决方案2】:

调试的方法是在挂起期间暂停调试器。在那里你会发现一些序列化程序阻塞了Task.Result 或类似的。

您使用IEnumerable&lt;Task&gt; 填充Data 属性。序列化程序可能会在某个时候调用Result,这就是典型的 ASP.NET 死锁。您可能应该用await Task.WhenAll(x.Select(...)) 包装Select 调用。

即使这样,同时运行这些 lambda 也可能是不安全的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-08
    • 2011-03-01
    • 2017-12-06
    相关资源
    最近更新 更多