在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请求(来自客户端),但在前端它永远不会返回。有僵局吗?
根据一些评论和@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也可能是不安全的