HttpClient.GetAsync(...) never returns when using await/async
You are misusing the API.
Here's the situation: in ASP.NET, only one thread can handle a request at a time. You can do some parallel processing if necessary (borrowing additional threads from the thread pool), but only one thread would have the request context (the additional threads do not have the request context).
This is managed by the ASP.NET SynchronizationContext
.
By default, when you await
a Task
, the method resumes on a captured SynchronizationContext
(or a captured TaskScheduler
, if there is no SynchronizationContext
). Normally, this is just what you want: an asynchronous controller action will await
something, and when it resumes, it resumes with the request context.
So, here's why test5
fails:
Test5Controller.Get
executesAsyncAwait_GetSomeDataAsync
(within the ASP.NET request context).AsyncAwait_GetSomeDataAsync
executesHttpClient.GetAsync
(within the ASP.NET request context).- The HTTP request is sent out, and
HttpClient.GetAsync
returns an uncompletedTask
. AsyncAwait_GetSomeDataAsync
awaits theTask
; since it is not complete,AsyncAwait_GetSomeDataAsync
returns an uncompletedTask
.Test5Controller.Get
blocks the current thread until thatTask
completes.- The HTTP response comes in, and the
Task
returned byHttpClient.GetAsync
is completed. AsyncAwait_GetSomeDataAsync
attempts to resume within the ASP.NET request context. However, there is already a thread in that context: the thread blocked inTest5Controller.Get
.- Deadlock.
Here's why the other ones work:
- (
test1
,test2
, andtest3
):Continuations_GetSomeDataAsync
schedules the continuation to the thread pool, outside the ASP.NET request context. This allows theTask
returned byContinuations_GetSomeDataAsync
to complete without having to re-enter the request context. - (
test4
andtest6
): Since theTask
is awaited, the ASP.NET request thread is not blocked. This allowsAsyncAwait_GetSomeDataAsync
to use the ASP.NET request context when it is ready to continue.
And here's the best practices:
- In your "library"
async
methods, useConfigureAwait(false)
whenever possible. In your case, this would changeAsyncAwait_GetSomeDataAsync
to bevar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- Don't block on
Task
s; it'sasync
all the way down. In other words, useawait
instead ofGetResult
(Task.Result
andTask.Wait
should also be replaced withawait
).
That way, you get both benefits: the continuation (the remainder of the AsyncAwait_GetSomeDataAsync
method) is run on a basic thread pool thread that doesn't have to enter the ASP.NET request context; and the controller itself is async
(which doesn't block a request thread).
More information:
- My
async
/await
intro post, which includes a brief description of howTask
awaiters useSynchronizationContext
. - The Async/Await FAQ, which goes into more detail on the contexts. Also see Await, and UI, and deadlocks! Oh, my! which does apply here even though you're in ASP.NET rather than a UI, because the ASP.NET
SynchronizationContext
restricts the request context to just one thread at a time. - This MSDN forum post.
- Stephen Toub demos this deadlock (using a UI), and so does Lucian Wischik.
Update 2012-07-13: Incorporated this answer into a blog post.
await HTTPClient.GetAsync never completing
Make sure to use .ConfigureAwait(false)
with each of your await
. Waiting synchronously on a task that internally awaits on the current synchronization context is a well-known cause of deadlocks in ASP.NET.
For instance, instead of:
await GetAPIAsync();
Do:
await GetAPIAsync().ConfigureAwait(false);
Of course, it would be even better if you didn't wait synchronously on a task to begin with.
The issue does not happen on ASP.NET Core because it doesn't use a custom synchronization context.
HttpClient not throwing exception when using await on GetAsync
My intuition tells me that you're calling Wait
or Result
further up your call stack.
If that is correct, then you're causing a deadlock, as I explain on my blog.
GetAsync : not returning HttpResponseMessage
You are blocking the UI thread and causing a deadlock. From Stephen Cleary's blog (Just replace GetJsonAsync
with your LoginUser
method, and GetStringAsync
with client.GetAsync
):
So this is what happens, starting with the top-level method
(Button1_Click for UI / MyController.Get for ASP.NET):
The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the
GetJsonAsync method later. GetJsonAsync returns an uncompleted Task,
indicating that the GetJsonAsync method is not complete.The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
… Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for
the context to be free so it can complete.
And the simple available solutions (also from the blog):
- In your “library” async methods, use ConfigureAwait(false) wherever possible.
- Don’t block on Tasks; use async all the way down.
The 2nd solution suggests that you change button1_Click
into:
private async void button1_Click(object sender, EventArgs e)
{
if ((await LoginUser(tUser.Text, Password.Text)).IsSuccessStatusCode)
{
Notifier.Notify("Successfully logged in.. Please wait!");
}
else
{
Notifier.Notify("Please check your Credential..");
}
}
HttpClient.GetAsync never returns on Xamarin.Android
It seems like you are experiencing a deadlock of some sort. You might want to include the code where you actually call GetInformationAsync
, as it is probably where the problem source is.
You can probably fix your issue by:
- Not calling
GetInformationAsync
in a sync way - Postfixing your async calls in
GetInformationAsync
withConfigureAwait(false)
to not switch context on every method call.
So your GetInformationAsync
method would look like:
public static async Task<MyClass> GetInformationAsync(string accountId)
{
var response = await Client.GetAsync(UriData + "/" + accountId).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<MyClass>(responseContent);
}
Then if you call it somewhere you need it to return back on the same context, I.e. if you need to update UI:
var myClass = await GetInformationAsync(accountId);
// update UI here...
Otherwise if you don't need to return on the same context:
var myClass = await GetInformationAsync(accountId).ConfigureAwait(false);
Related Topics
What Is the Purpose of a Question Mark After a Value Type (For Example: Int? Myvariable)
Client to Send Soap Request and Receive Response
Deciding Between Httpclient and Webclient
Integer Summing Blues, Short += Short Problem
C# Code to Validate Email Address
Why Doesn't C# Infer My Generic Types
Is a Linq Statement Faster Than a 'Foreach' Loop
Can My Enums Have Friendly Names
How to Compile a Windows.Forms Application on Linux for .Net Core
Creating a Dpi-Aware Application
Replace Line Breaks in a String C#
Ipc Mechanisms in C# - Usage and Best Practices
When Should I Use a List VS a Linkedlist
How to Get the List of Open File Handles by Process in C#