3

I am return a list which basically calls the two async operations:

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

It is an ajax request (from client side) but on front-end it never returns. Is there any a deadlock ?

9
  • 4
    How are you awaiting calls when not inside a method that (a) is not marked as async and (b) does not return Task<ActionResult>? Commented Oct 17, 2015 at 11:15
  • Because its a lamda expression. If i marke the method async Task<ActionResult> the compiler reports that my method lacks await operator. Even if ii put it, it doesn't work. Commented Oct 17, 2015 at 11:18
  • Cool, I just saw the async delegate. That's the problem by the looks of it. Is Data a Task<T> where T is the x.ToModel() type? Commented Oct 17, 2015 at 11:20
  • no, Data = IEnumberable while ToModel() is a ViewModel i.e ProductModel() Commented Oct 17, 2015 at 11:22
  • 1
    Pretty sure it will be an IEnumerable<Task> because of the async delegate passed to .Select(). This means that it will not be waiting for the task to complete. If you await the categories.Select() call by wrapping in a Task.WhenAll(), mark the action as async and return Task<ActionResult>, I believe this should solve your problem Commented Oct 17, 2015 at 11:24

2 Answers 2

9

Building up my answer from several comments and @usr's answer:

  • Data in the code above is actually IEnumerable<Task<ProductModel>>, not IEnumerable<ProductModel>. This is because the lambda passed to Select is async.
  • Most likely, the JSON serializer is going over this structure and enumerating the properties on the Task<ProductModel> instances, including Result.

I explain on my blog why accessing Result will cause a deadlock in this case. In a nutshell, it's because the async lambda will attempt to resume execution on the ASP.NET request context after its await. However, the ASP.NET request context is blocked on the call to Result, locking a thread inside that request context until the Task<T> completes. Since the async lambda cannot resume, it cannot complete that task. So both things are waiting for each other, and you get a classic deadlock.

There are some suggestions to use await Task.WhenAll, which I would normally agree with. However, in this case you're using Entity Framework and got this error:

A second operation started on this context before a previous asynchronous operation completed.

This is because EF cannot perform multiple asynchronous calls concurrently within the same db context. There are a couple ways around this; one is to use multiple db contexts (essentially multiple connections) to do the calls concurrently. IMO a simpler way is to make the asynchronous calls sequentially instead of concurrently:

[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);
}
Sign up to request clarification or add additional context in comments.

2 Comments

Async LINQ would be a nice thing to have here.
Great summary Stephen :)
2

The way to debug is is to pause the debugger during the hang. There you will find that some serializer is blocking on Task.Result or similar.

You fill the Data property with a IEnumerable<Task>. The serializer probably calls Result at some point and that's the classic ASP.NET deadlock. You probably should wrap the Select call with await Task.WhenAll(x.Select(...)).

And even then it might be unsafe to concurrently run those lambdas.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.