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