DarthVader
DarthVader

Reputation: 55112

Async await really waits a lot

public class GeoHelper
{ 
    const string GeoIpUrl = "http://freegeoip.net/json/{0}";
    private readonly string _ipAddress = string.Empty;

    public GeoHelper(string ip)
    {
        _ipAddress = ip;
    }

    public async Task<string> GetGeoAsync()
    {
        string uri = string.Format(GeoIpUrl, _ipAddress);

        var httpClient = new HttpClient();

        var response = await httpClient.GetAsync(uri);

        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();

        return content;
    }

}

Then I am calling it as follows:

    [ChildActionOnly]
    public ActionResult UserGeo()
    {
        var ip = RequestHelper.GetClientIpAddress(Request);

        var geoHelper = new GeoHelper(ip);

        var response = geoHelper.GetGeoAsync();

        var result = response.Result;

        var resultobj = JsonConvert.DeserializeObject<GeoInfo>(result);

        return Content(resultobj.city);
    }

at var result = response.Result; it waits and never ends, i get a grey hair while waiting. I have a small console app it works fine there. same code.

Why? how can i fix?

Upvotes: 2

Views: 784

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457197

Unfortunately, asynchronous child actions are not currently supported in MVC. Please vote on this issue (CodePlex, UserVoice).

I explain the deadlock issue on my blog. Essentially, it's because ASP.NET only allows one thread at a time in the request context, and when you block a thread in the request context (using Result), then any async methods that are attempting to re-enter that context cannot complete.

There are various hacks you can use, but the cleanest solution is to just use synchronous code if you can. Another solution is to use Result, but in that case you have to make sure every await in GetGeoAsync (and every async method it calls) uses ConfigureAwait(false), which means you can't use the HttpContext, request, or response.

A third solution, which is rather hacky, is to use AsyncContext.Run from my AsyncEx library:

[ChildActionOnly]
public ActionResult UserGeo()
{
  return AsyncContext.Run(async () =>
  {
    var ip = RequestHelper.GetClientIpAddress(Request);
    var geoHelper = new GeoHelper(ip);
    var response = geoHelper.GetGeoAsync();
    var result = await response;
    var resultobj = JsonConvert.DeserializeObject<GeoInfo>(result);
    return Content(resultobj.city);
  }
}

However, the AsyncContext approach does not work for all code. It sets up a kind of "nested loop" but does not use AspNetSynchronizationContext, so some ASP.NET code does not like running within AsyncContext.

Upvotes: 5

ZombieSheep
ZombieSheep

Reputation: 29963

Try this...

[ChildActionOnly]
public async Task<ActionResult> UserGeo()
{
    var ip = RequestHelper.GetClientIpAddress(Request);
    var geoHelper = new GeoHelper(ip);
    var result = await geoHelper.GetGeoAsync();
    var resultobj = JsonConvert.DeserializeObject<GeoInfo>(result);
    return Content(resultobj.city);
}

Upvotes: 0

Related Questions