我遇到了HttpClient调用的死锁问题。我猜测僵局是由于我对异步编程的不完全了解而导致的,并且我做错了什么。我将实际代码简化为下面的示例,但是我保持了相同的方法调用顺序和结构。对于可能导致僵局的某些内幕消息以及如何克服此问题的建议,我将不胜感激。
我为HttpClient创建了一个扩展类,该类具有以下方法:
//HttpClientExtension class
public async static Task<HttpResponseMessage> PostAsyncCustom(this HttpClient client, Uri uri, HttpContent request, bool tryReauth)
{
var originalResponse = await client.PostAsync(uri, request);
var content = await originalResponse.Content.ReadAsStringAsync();
if(tryReauth)
{
//check if session expired and login again
var login = await App.Communication.Login();
//throw exception with the reauth status
if(login)
{
throw new AuthException(true);
}
else
{
throw new AuthException(false);
}
}
}
public async static Task<HttpResponseMessage> PostAsyncWithReauth(this HttpClient client, Uri uri, HttpContent request)
{
return await PostAsyncCustom(client, uri, request, true);
}
public async static Task<HttpResponseMessage> PostAsyncWithoutReauth(this HttpClient client, Uri uri, HttpContent request)
{
return await PostAsyncCustom(client, uri, request, false);
}
此方法在我的Communication类中使用,看起来像这样:
//Communication class
//HttpClient is defined in contstructor
public async Task<bool> Login()
{
//Define Uri and Request
var response = await client.PostAsyncWithoutReauth(uri, request);
//Check response status and return success/failure
return true;
}
public async Task<bool> FetchData()
{
//Define Uri and Request
try
{
var response = await client.PostAsyncWithReauth(uri, request);
}
catch(AuthException ae)
{
//if reauth was successfull
if(ae)
{
var newResponse = await client.PostAsyncWithoutReauth(uri, request);
//Check newResponse status and return success/failure
}
}
return false;
}
所以发生的情况是,当抛出状态为true的AuthException
并调用PostAsyncWithoutReauth
时,会触发PostAsyncWithoutReauth
内部的断点,但是在继续操作时,PostAsyncCustom
内的断点将不会触发,PostAsyncWithoutReauth
被调用。该代码将继续执行,直到任务超时异常被触发为止。所以这就是为什么我认为这是一个僵局问题。我曾尝试在某些电话上设置.ConfigureAwait(false)
,但问题仍然相同。
更新:
从Xamarin.Forms ContentPage调用FetchData
方法,该方法由Entry字段上的complete事件触发:
Input.Completed += async (s, e) =>
{
var status = await App.Communication.FetchData();
}
我想补充一下,如果我将PostAsyncWithoutReauth
调用替换为return await FetchData()
,则不会死锁。
更新#2:
我简化了代码,所以我的PostAsyncCustom
现在看起来像这样:
public async static Task<HttpResponseMessage> PostAsyncCustom(this HttpClient client, Uri uri, HttpContent request, bool tryReauth)
{
var originalResponse = await client.PostAsync(uri, request);
var content = await originalResponse.Content.ReadAsStringAsync();
if(tryReauth)
{
return await PostAsyncCustom(client, uri, request, false);
}
return originalResponse;
}
这里发生的是client.PostAsync
在第二次被调用时将挂起。我还试图摆脱递归,所以我测试了这样的代码:
public async static Task<HttpResponseMessage> PostAsyncCustom(this HttpClient client, Uri uri, HttpContent request, bool tryReauth)
{
var originalResponse = await client.PostAsync(uri, request);
var content = await originalResponse.Content.ReadAsStringAsync();
if(tryReauth)
{
var secondReponse = await client.PostAsync(uri, request);
}
return originalResponse;
}
这将在第二次调用client.PostAsync
时挂起。
更新#3:
“死锁”最终引发stacktrace异常,可以here看到。