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;
}
}
然后我按如下方式调用它:
[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);
}
在var result = response.Result;
它等待并且永远不会结束,我在等待时得到一头白发。我有一个小的控制台应用程序,它在那里很好用。相同的代码。
为什么呢?我该怎么办?
答案 0 :(得分:5)
不幸的是,MVC目前不支持异步子操作。请就此问题投票(CodePlex,UserVoice)。
我解释了死锁问题on my blog。从本质上讲,这是因为ASP.NET在请求上下文中一次只允许一个线程,当你在请求上下文中阻塞线程时(使用Result
),那么任何async
方法都试图重新输入上下文无法完成。
您可以使用各种黑客,但最干净的解决方案是尽可能使用同步代码。另一种解决方案是使用Result
,但在这种情况下,您必须确保await
(以及它调用的每个GetGeoAsync
方法)中的每个async
都使用ConfigureAwait(false)
,这意味着您无法使用HttpContext
,请求或回复。
第三个解决方案,相当苛刻,是使用AsyncEx library中的AsyncContext.Run
:
[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);
}
}
但是,AsyncContext
方法并不适用于所有代码。它设置了一种“嵌套循环”但不使用AspNetSynchronizationContext
,因此一些ASP.NET代码不喜欢在AsyncContext
内运行。
答案 1 :(得分:0)
试试这个......
[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);
}