I am experiencing a deadlock issue with HttpClient calls. I am guessing the deadlock is the result of my incomplete knowledge about async programming and I am doing something wrong. I have simplified the actual code into the example below, but I have maintained the same method call sequence and structure. I will be grateful for some insides on what could be causing the deadlock and suggestions on how to overcome this issue.
I created an extension class for HttpClient, which has the following methods:
//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);
}
This methods are used inside my Communication class which looks something like this:
//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;
}
So what happens is that when an AuthException
is thrown with status = true and PostAsyncWithoutReauth
will be called, what happens is that the break-point inside PostAsyncWithoutReauth
will be triggered, but upon continuation the break-point inside PostAsyncCustom
which is called from the PostAsyncWithoutReauth
never triggers. The code will just continue to "execute" until Task timeout exception will be triggered. So that's why I think it is a deadlock issue. I have tried setting the .ConfigureAwait(false)
on some of the calls, but the issue remained the same.
Update:
The FetchData
method is called from the Xamarin.Forms ContentPage, where it is triggered by completed event on an Entry field:
Input.Completed += async (s, e) =>
{
var status = await App.Communication.FetchData();
}
I would like to add that if instead the PostAsyncWithoutReauth
call I replace it with return await FetchData()
, this will not deadlock.
Update #2:
I simplified the code, so my PostAsyncCustom
now looks like that:
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;
}
What happens here is that the client.PostAsync
will hang when called for the second time. I also tried to get rid of the recursion so I tested the code like that:
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;
}
This will also hang when the client.PostAsync
is called for the second time.
Update #3:
The "deadlock" finally throws an exception with stacktrace, which can be seen here.