Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Async / Await never responding in MVC4

I've been struggling with async / await for a week now. Some background: the code below is part of an MVC4 website project. The website has a large number of API calls happening. The goal is to get those API calls happening in parallel instead of synchronous to improve site responsiveness. Right now all the API calls block each other. So if one page requires 4 calls...longer load time.

I've built out individual methods for both the synchronous and async versions of all the API calls. The problem I have is the await never responds. I think it's related to this question. However, I'm really not sure how to solve it. I've tried the ConfigureAwait(false), and that hasn't helped me.

Here's the code:

The initial call in the controller it looks like so:

BaseData bdata = API.GetBaseData().Result;

I'd love to use await here, but that's not an option without an AsyncController, which we can't use due to needing request / response access. The other methods are in the API class:

internal static async Task<BaseData> GetBaseData() {
    var catTask = GetParentCategoriesAsync();
    var yearTask = GetYearsAsync();

    await Task.WhenAll(new Task[] { catTask, yearTask });

    var bdata = new BaseData {
        years = await yearTask,
        cats = await catTask
    };
    return bdata;
}

internal static async Task<List<APICategory>> GetParentCategoriesAsync() {
    try {
        WebClient wc = new WebClient();
        wc.Proxy = null;

        string url = getAPIPath();
        url += "GetFullParentCategories";
        url += "?dataType=JSON";

        Uri targeturi = new Uri(url);
        List<APICategory> cats = new List<APICategory>();

        var cat_json = await wc.DownloadStringTaskAsync(targeturi);
        cats = JsonConvert.DeserializeObject<List<APICategory>>(cat_json);

        return cats;
    } catch (Exception) {
        return new List<APICategory>();
    }
}

internal static async Task<List<double>> GetYearsAsync() {
    WebClient wc = new WebClient();
    wc.Proxy = null;
    Uri targeturi = new Uri(getAPIPath() + "getyear?dataType=JSON");
    var year_json = await wc.DownloadStringTaskAsync(targeturi);
    List<double> years = JsonConvert.DeserializeObject<List<double>>(year_json);
    return years;
}

When these methods are called, I can put breakpoints in GetYearsAsync() and GetParentCategoriesAsync(). Everything fires until the await wc.DownloadStringTaskAsync(targeturi) command. That's where stops.

I've added ConfigureAwait(continueOnCapturedContext: false) to all the tasks, but that hasn't helped. I'm assuming the problem is that the threads are not in the same context. However, I'm not certain. I am certain, however, that I'm doing something wrong. I'm just not sure what. It's either that or I'm just trying to do something that can't be done with .NET MVC4. Any thoughts would be supremely appreciated.

like image 373
janiukjf Avatar asked Nov 02 '22 20:11

janiukjf


1 Answers

The problem is actually due to WebClient, which will always sync back to the request context (which is blocked due to the Result call).

You can use HttpClient instead, combined with ConfigureAwait(false):

internal static async Task<BaseData> GetBaseDataAsync() {
  var catTask = GetParentCategoriesAsync();
  var yearTask = GetYearsAsync();

  await Task.WhenAll(catTask, yearTask).ConfigureAwait(false);

  var bdata = new BaseData {
    years = await yearTask,
    cats = await catTask
  };
  return bdata;
}

internal static async Task<List<APICategory>> GetParentCategoriesAsync() {
  try {
    var client = new HttpClient();

    string url = getAPIPath();
    url += "GetFullParentCategories";
    url += "?dataType=JSON";

    Uri targeturi = new Uri(url);
    List<APICategory> cats = new List<APICategory>();

    var cat_json = await client.GetStringAsync(targeturi).ConfigureAwait(false);
    cats = JsonConvert.DeserializeObject<List<APICategory>>(cat_json);

    return cats;
  } catch (Exception) {
    return new List<APICategory>();
  }
}

internal static async Task<List<double>> GetYearsAsync() {
  var client = new HttpClient();
  Uri targeturi = new Uri(getAPIPath() + "getyear?dataType=JSON");
  var year_json = await client.GetStringAsync(targeturi).ConfigureAwait(false);
  List<double> years = JsonConvert.DeserializeObject<List<double>>(year_json);
  return years;
}

That should enable you to call it as such:

BaseData bdata = API.GetBaseDataAsync().Result;

However, I strongly recommend that you call it as such:

BaseData bdata = await API.GetBaseDataAsync();

You'll find that the code both before and after the await can access the request and response context just fine.

like image 128
Stephen Cleary Avatar answered Nov 12 '22 20:11

Stephen Cleary