在Asp中,异步调用永远不会返回.净MVC

本文关键字:返回 MVC 永远 调用 Asp 异步 | 更新日期: 2023-09-27 18:18:11

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

[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请求(来自客户端),但在前端它永远不会返回。有僵局吗?

在Asp中,异步调用永远不会返回.净MVC

根据一些评论和@usr的回答构建我的答案:

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

我在我的博客上解释了为什么访问Result在这种情况下会导致死锁。简而言之,这是因为async lambda将尝试在ASP上恢复执行。. NET请求上下文在其await之后。然而,ASP。. NET请求上下文在调用Result时被阻塞,锁定该请求上下文中的线程,直到Task<T>完成。由于async lambda无法恢复,因此它无法完成该任务。所以这两个东西都在等待对方,你就得到了一个典型的死锁。

有一些建议使用await Task.WhenAll,我通常会同意。然而,在这种情况下,你正在使用实体框架,并得到这个错误:

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

这是因为EF不能在同一个db上下文中并发地执行多个异步调用。有几种方法;一种是使用多个db上下文(本质上是多个连接)并发地执行调用。在我看来,更简单的方法是按顺序而不是并发地进行异步调用:

[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);
}

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

IEnumerable<Task>填充Data属性。序列化程序可能在某个时候调用Result,这是典型的ASP。净僵局。您可能应该用await Task.WhenAll(x.Select(...))封装Select调用。

即使这样,并发运行这些lambdas也可能是不安全的