O. R. Mapper
O. R. Mapper

Reputation: 20760

Why do HTTP requests never return with async await?

I am trying to conduct an HTTP call to another server from within an ASP.NET application run on IIS 8.5.

To get started, I took some hints from an article by Microsoft, Call a Web API From a .NET Client (C#). I could easily see a pattern of how they make HTTP calls there; to show just one shortened example:

    static async Task<Product> GetProductAsync(string path)
    {
        HttpResponseMessage response = await client.GetAsync(path);
        if (response.IsSuccessStatusCode)
        {
            // retrieve response payload
            ... = await response.Content.ReadAsAsync<...>();
        }
        // do something with data
    }

Easy enough, I thought, so I quickly wrote a similar method for my application (note that the ReadAsAsync extension method appears to require an additional library, so I chose one of the built-in, more abstract, but otherwise presumeably analogous methods):

    private async Task<MyInfo> RetrieveMyInfoAsync(String url)
    {
        var response = await HttpClient.GetAsync(url);

        response.EnsureSuccessStatusCode();
        var responseBody = await response.Content.ReadAsStringAsync();

        return JsonConvert.DeserializeObject<MyInfo>(responseBody);
    }

Unfortunately, calling this method will cause my application to hang. When debugging, it turns out that the await call to GetAsync never returns.

After searching around for a bit, I stumbled over a remotely similar issue, in whose comments section I found a very interesting suggestion by Mr. B:

Remove all the async stuff and make sure it works.

So I gave it a try:

    private Task<MyInfo> RetrieveMyInfoAsync(String url)
    {
        return HttpClient.GetAsync(url).ContinueWith(response =>
        {
            response.Result.EnsureSuccessStatusCode();
            return response.Result.Content.ReadAsStringAsync();
        }).ContinueWith(str => JsonConvert.DeserializeObject<MyInfo>(str.Result.Result));
    }

Somewhat surprisingly (to me), this works. GetAsync returns the expected response from the other server within less than a second.

Now, working with AngularJS at the same time, I am a bit disappointed by things like response.Result.Content and str.Result.Result. In AngularJS, I'd expect the above call to be simply something like:

$http.get(url).then(function (response) {
    return response.data;
});

Even if we discount the automatic JSON deserialization that's happening in JavaScript, the AngularJS code is still easier as e.g. response is not wrapped into a promise or anything like that, nor will I end up with a structure like Task<Task<...>> when returning another promise from within the continuation function.

Therefore, I am not very happy with having to use this ContinuesWith syntax rather than the more readable async-await pattern, if the latter just worked.

What am I doing wrong in the async-await variant of my C# HTTP call?

Upvotes: 5

Views: 5934

Answers (1)

V0ldek
V0ldek

Reputation: 10593

So judging by the fact that ConfigureAwait(false) helped with your issue, please, read these from Stephen Cleary's blog: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

The guy's pretty much an async expert (he wrote the Concurrency in C# Cookbook book), so whatever I can say he probably explains better. Basically you're blocking the ASP.NET thread somewhere, maybe not using await all the way but rather Wait, Result or GetResult(). You should be able to diagnoze the issue yourself using that blog.

What ConfigureAwait(false) does is it does not capture the current context, so the HTTP request gets performed (correctly) somewhere else than on the ASP.NET context, preventing a deadlock.

EDIT:

GetAwaiter().GetResult() is what's causing the issue, judging by your comment. If you changed that to await and the calling method to async you'd probably fix everything.

Since C# 7.0 and async Task Main() method support there's really no reason to block instead of using await in your application code, ever.

Upvotes: 8

Related Questions