从Xamarin事件中调用时,HttpClient的C#异步死锁是为什么?

时间:2018-10-24 13:16:58

标签: c# asynchronous xamarin.forms httpclient deadlock

我遇到了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看到。

0 个答案:

没有答案