C# HttpClient.GetStringAsync()调用冻结所有内容而不抛出错误,但仅在特定条件下
我正在编写一个Windows通用应用程序,它严重依赖REST服务提供的信息。为了实现这一点(并提供缓存支持),我正在编写一个附带的库,应用程序将调用该库。代码的核心是以下功能: PokeLib.Utilities:C# HttpClient.GetStringAsync()调用冻结所有内容而不抛出错误,但仅在特定条件下,c#,multithreading,windows-runtime,async-await,win-universal-app,C#,Multithreading,Windows Runtime,Async Await,Win Universal App,我正在编写一个Windows通用应用程序,它严重依赖REST服务提供的信息。为了实现这一点(并提供缓存支持),我正在编写一个附带的库,应用程序将调用该库。代码的核心是以下功能: PokeLib.Utilities: private static HttpClient fetcher = new HttpClient(); public static async Task<JObject> GetPokemon(int n) { return
private static HttpClient fetcher = new HttpClient();
public static async Task<JObject> GetPokemon(int n)
{
return await GetData(prefix + "pokemon/" + n + "/");
}
public static async Task<JObject> GetType(int n)
{
return await GetData(prefix + "type/" + n + "/");
}
private static Dictionary<string, JObject> resourceCache = new Dictionary<string, JObject>(); // TODO: Stale check
public static async Task<JObject> GetData(string resourcePath, bool forceRefresh = false)
{
if (!resourceCache.ContainsKey(resourcePath) || forceRefresh)
resourceCache[resourcePath] = await FetchDataFromServer(resourcePath);
return resourceCache[resourcePath];
}
public static async Task<JObject> FetchDataFromServer(string resourcePath)
{
try
{
string json = await fetcher.GetStringAsync(baseUri + resourcePath); // ***** this is the line that dies *****
return JObject.Parse(json);
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return null;
}
}
public Type(int id, bool constructNow = false)
{
this.id = id;
try
{
if (constructNow) // this is being called with true
Create().Wait();
}
catch (Exception e)
{
throw e;
}
}
public override async Task Create()
{
JObject data = await Utilities.GetType(id);
if (data == null)
throw new KeyNotFoundException("Type ID #" + id + " does not exist");
name = (string)data["name"];
id = (int)data["id"];
resourcePath = (string)data["resource_uri"];
}
调用Type.cs的代码实际上与Pokemon.cs的代码完全相同(包括.Wait()),因此我认为这不是问题所在
有什么帮助/想法吗?谢谢 我对
WinRT
不太熟悉,但这看起来像是一个典型的死锁,因为WinRTSynchronizationContext
在您的每个调用中流动
为了完全避免这种情况,可以使用InitializeAsync
模式,因为构造函数不能是异步的
模式很简单,在构造对象之后,让调用方调用InitializeAsync
方法:
public Type(int id, bool constructNow = false)
{
this.id = id;
this.constructNow = constructNow;
}
public Task InitializeAsync()
{
return constructNow ? Create() : Task.FromResult(false);
}
然后称之为:
var type = new Type(1, true);
await type.InitializeAsync();
在为外部库实现代码时,建议使用调用asyncwait
模式中的异步方法。它所做的是显式抑制同步上下文的流,以避免用户使用Task.Result
或Task.Wait调用异步方法时出现潜在错误:
string json = await fetcher.GetStringAsync(baseUri + resourcePath).ConfigureAwait(false);
在整个调用链中使用此选项。完全没有关于fetcher
的信息,代码示例中没有GetItem()
方法?即使这些显然是你问题中最重要的部分?看啊,哎呀GetItem()
应该是GetType()
,而fetcher
是一个HttpClient(正如标题所暗示的,但我应该明确提到的那样)。已修复。您是否在任何顶级方法中调用Task.Result
或Task.Wait
?发布一个如何?我使用的是.Wait()(可能不正确)作为一种打破不断冒泡的等待异步的方法。我是否应该尝试尽快放弃异步性,并将其一直保留到UI的代码?没错。异步性需要从UI事件处理程序开始,一路冒泡。不要压制它,拥抱它。酷!这似乎对我来说很重要通常是这样做的,但现在还有其他一些事情有困难,但我认为这更适合单独的帖子: