Gabor Varga
Gabor Varga

Reputation: 95

C# Xamarin does not receive response from HttpClient

I have the following method in C# Xamarin for authentication:

private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
    {
        HttpRequestMessage request = new HttpRequestMessage(method, url);
        request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
        if (!string.IsNullOrEmpty(this._AuthToken))
        {
            request.Headers.Add("Authorization", this._AuthToken);
        }
        return await this._Client.SendAsync(request);
    }

The server receives the request, and the authentication is success (appears in server log). Based on wireshark, many response arrives back from the server, but the function is not returning. The application freezes.

Any idea? This method is working on a normal console client without any problem.

No error message, just a freezed client.

Thanks your answer!

Upvotes: 0

Views: 608

Answers (1)

Cheesebaron
Cheesebaron

Reputation: 24460

What you are experiencing here is a classical case of dead-locking. In the comments I suggested to simply remove the async and await keywords and simply returning the Task the HttpClient.SendAsync method returns. Why does this change anything? Let me explain.

Right before you await a async method the current SynchronizationContext is captured. In your case, it is probably the UI Thread.

Then when the actual SendAsync method is done doing its work, it will try to return to that SynchronizationContext. This is usually fine. However, it really depends on how you actually call the encapsulating method.

If you do something like: _SendAsync(..).Result from a UI thread, this will most likely result in a dead-lock. Since the .Result is a blocking call the UI thread will wait for .Result to finish. Since, the UI thread is waiting, returning to the UI Thread SynchronizationContext after the awaited SendAsync method is done won't work because the UI Thread is blocked.

So what does simply returning the Task in this case do? Since you are not awaiting the Task and the method isn't marked async, the responsibility of the context switching is moved up the stack to where _SendAsync is called. Calling .Result here won't do the context switching, but just make a blocking call and losing all the benefits of doing async/await to begin with.

Alternatively, you could also add .ConfigureAwait(false) to the awaited this._Client.SendAsync() call. What this does, is to not return to the capture SynchronizationContext and just return to whatever ThreadPool thread it was executed on.

In either case, be very careful with using .Result or .GetAwaiter().GetResult(), in most cases it should be avoided.

Tasks should normally be awaited. Or if you don't really care about the result you can fire and forget with Task.Run().

Most service calls and async calls that don't need to return to the captured context, can mostly be post-fixed with ConfigureAwait(false). The main reason to not have it is if you want to update UI just after.

You can read much more about ConfigureAwait in the FAQ by Stepen Toub

I highly recommend you read the excellent documentation about async/await in the MS Docs

TL;DR:

Either do:

private async Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
    HttpRequestMessage request = new HttpRequestMessage(method, url);
    request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
    if (!string.IsNullOrEmpty(this._AuthToken))
    {
        request.Headers.Add("Authorization", this._AuthToken);
    }
    return await this._Client.SendAsync(request).ConfigureAwait(false);
}

Or do:

private Task<HttpResponseMessage> _SendAsync(HttpMethod method, string url, object obj)
{
    HttpRequestMessage request = new HttpRequestMessage(method, url);
    request.Content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");
    if (!string.IsNullOrEmpty(this._AuthToken))
    {
        request.Headers.Add("Authorization", this._AuthToken);
    }
    return this._Client.SendAsync(request);
}

To fix the problem.

And avoid calling .Result on Tasks. Always await them.

Upvotes: 2

Related Questions