1

After considering this interesting answer HttpClient.GetAsync(...) never returns..., I still have a situation where my HttpClient is not returning when I use await (sample code below). In addition. I use this helper routine from both asp.net MVC5 Controllers (UI-driven) and the WebApi. What can I do to:

  1. Use await instead of the (dreaded) .Result and still have this function return?
  2. Reuse this same routine from both MVC Controllers and WebApi?

Apparently, I should replace the .Result with .ConfigureAwait(false) but this seems to contradict with the fact that "Task4" in the post cited above works fine with an await httpClient.GetAsync. Or do I need separate routines for Controller and WebApi cases?

public static async Task<IEnumerable<TcMarketUserFullV1>> TcSearchMultiUsersAsync(string elasticQuery)
{
    if (string.IsNullOrEmpty(elasticQuery)) return null;

    IEnumerable<TcMarketUserFullV1> res = null;
    using (var hclient = new HttpClient())
    {
        hclient.BaseAddress = new Uri("https://addr.servicex.com");
        hclient.DefaultRequestHeaders.Accept.Clear();
        hclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        hclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
            CloudConfigurationManager.GetSetting("jwt-bearer-token"));

        // Why does this never return when await is used?
        HttpResponseMessage response =  hclient.GetAsync("api/v2/users?q=" + elasticQuery + "&search_engine=v2").Result; 
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            res = JsonConvert.DeserializeObject<TcMarketUserFullV1[]>(content).AsEnumerable();
        }
        else{log.Warn("...");}
    } 
    return res;
} 

UPDATE: My call chain, which starts with a Telerik Kendo Mvc.Grid DataBinding call is as follows:

[HttpPost]
public async Task<ActionResult> TopLicenseGrid_Read([DataSourceRequest]DataSourceRequest request)
{
    var res = await GetLicenseInfo();
    return Json(res.ToDataSourceResult(request)); // Kendo.Mvc.Extensions.DataSourceRequest
}

Then:

private async Task<IEnumerable<CsoPortalLicenseInfoModel>> GetLicenseInfo()
{
  ...
  // Never returns
  var qry = @"app_metadata.tc_app_user.country:""DE""";
  return await TcSearchMultiUsersAsync(qry);
}

Then, the routine shown in full above but now WITHOUT the .Result:

public static async Task<IEnumerable<TcMarketUserFullV1>> TcSearchMultiUsersAsync(string elasticQuery)
{
    if (string.IsNullOrEmpty(elasticQuery)) return null;

    IEnumerable<TcMarketUserFullV1> res = null;
    using (var hclient = new HttpClient())
    {
        hclient.BaseAddress = new Uri("https://addr.servicex.com");
        hclient.DefaultRequestHeaders.Accept.Clear();
        hclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        hclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
            CloudConfigurationManager.GetSetting("jwt-bearer-token"));

        // This now returns fine
        HttpResponseMessage response =  hclient.GetAsync("api/v2/users?search_engine=v2&q=" + elasticQuery"); 
        if (response.IsSuccessStatusCode)
        {
            // This returns my results fine too
            var content = await response.Content.ReadAsStringAsync();
            // The following line never returns results. When debugging, everything flows perfectly until I reach the following line, which never 
            // returns and the debugger returns me immediately to the top level HttpPost with a result of null. 
            res = JsonConvert.DeserializeObject<TcMarketUserFullV1[]>(content).AsEnumerable();
        }
        else{log.Warn("...");}
    } 
    return res;
} 
Community
  • 1
  • 1
GGleGrand
  • 1,565
  • 1
  • 20
  • 45
  • You're missing an `await` in `hclient.SendAsync`. – Yuval Itzchakov Nov 24 '15 at 06:59
  • I know. This is because it "hangs" when I put the await there. Note the comment above the code line. The only way it will deliver me return data is when I use the (dreaded) .Result. That is what I do not understand.... – GGleGrand Nov 26 '15 at 21:22

1 Answers1

4

You need to await everything. It's not enough that you await on one of them, you need to await on all of them:

This:

HttpResponseMessage response = hclient.GetAsync(
                        "api/v2/users?q=" + elasticQuery + "&search_engine=v2").Result;

Should be:

HttpResponseMessage response = await hclient.GetAsync(
                                "api/v2/users?q=" + elasticQuery + "&search_engine=v2");

It is enough to have one blocking call to .Result in order to deadlock. You need "async all the way".

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Thanks. But that is precisely the problem I have and not the answer. You see, when I remove the .Result and use await it hangs the thread and when I use .Result I at least get the results back from the httpClient call. – GGleGrand Nov 22 '15 at 17:23
  • @GGleGrand That sounds peculiar. Show the entire call-chain for `TcSearchMultiUsersAsync` – Yuval Itzchakov Nov 22 '15 at 17:32
  • The top level action was still ActionResult TopGrid_Read. Changing to public async Task TopGrid_Read appears to have resolved. Thanks! – GGleGrand Nov 23 '15 at 10:04
  • I spoke too soon. That it worked once appears to be a fluke. I'll try to add my call flow and observations to the original question. – GGleGrand Nov 23 '15 at 15:01
  • I have just confirmed that the asyn behavoir (await) differs between my Azure WebApp x32 and x64 (64bit) instances -- identical code. This is not directly a solution to my question above, but several obscure async "issues" I had disappeared once on a x64 machine. – GGleGrand Nov 25 '15 at 13:20
  • That sounds weird. What issues were you having exactly? – Yuval Itzchakov Nov 25 '15 at 13:20
  • A case for another thread I guess, but: external webhook calls to an async WebApi POST return Status 500 (with premature Async exit error) on 32x and Status 200 on 64x. Identical code. async void func(), which may be the problem. – GGleGrand Nov 25 '15 at 13:23
  • Can you create a new question and post a [mcve] of that problem? – Yuval Itzchakov Nov 25 '15 at 13:24
  • @GGleGrand Do that, should be an interesting question :) – Yuval Itzchakov Nov 25 '15 at 13:26
  • 1
    May be just a timing thing -- i.e. luck = time-bomb -- we'll see... test continue. – GGleGrand Nov 25 '15 at 13:40