Reputation: 95
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
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