Reputation: 20760
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
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