我返回一个基本上调用两个异步操作的列表:
[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请求(来自客户端),但在前端它永远不会返回。有没有僵局?
答案 0 :(得分:9)
从几条评论和@ usr的答案中建立我的答案:
Data
实际上是IEnumerable<Task<ProductModel>>
,而不是IEnumerable<ProductModel>
。这是因为传递给Select
的lambda是async
。Task<ProductModel>
实例上的属性,包括Result
。在这个案例中,我在我的博客why accessing Result
will cause a deadlock上进行了解释。简而言之,因为async
lambda将尝试在await
之后的ASP.NET请求上下文中继续执行。但是,在调用Result
时会阻止ASP.NET请求上下文,在Task<T>
完成之前锁定该请求上下文中的线程。由于async
lambda无法恢复,因此无法完成该任务。所以这两件事情都在相互等待,你会遇到经典的僵局。
有一些使用await Task.WhenAll
的建议,我通常会同意这些建议。但是,在这种情况下,您正在使用Entity Framework并出现此错误:
在上一次异步操作完成之前,在此上下文中启动了第二个操作。
这是因为EF不能在同一个db上下文中同时执行多个异步调用。有几种方法可以解决这个问题;一种是使用多个db上下文(实质上是多个连接)来同时进行调用。 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);
}
答案 1 :(得分:2)
调试的方法是在挂起期间暂停调试器。在那里,您会发现某些序列化程序在index.html
或类似的情况下阻塞。
您使用Task.Result
填充Data
媒体资源。串行器可能在某个时刻调用IEnumerable<Task>
,这是典型的ASP.NET死锁。您可能应该使用Result
打包Select
。
即便如此,同时运行这些lambdas也可能不安全。