HttpClient.GetAsync(...) never returns when using await/async
Since you are using .Result
or .Wait
or await
this will end up causing a deadlock in your code.
you can use ConfigureAwait(false)
in async
methods for preventing deadlock
like this:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
you can use
ConfigureAwait(false)
wherever possible for Don't Block Async Code .
Edit: Generally try to avoid doing the below except as a last ditch effort to avoid deadlocks. Read the first comment from Stephen Cleary.
Quick fix from here. Instead of writing:
Task tsk = AsyncOperation();
tsk.Wait();
Try:
Task.Run(() => AsyncOperation()).Wait();
Or if you need a result:
var result = Task.Run(() => AsyncOperation()).Result;
From the source (edited to match the above example):
AsyncOperation will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of AsyncOperation won’t be forced back to the invoking thread.
For me this looks like a useable option since I do not have the option of making it async all the way (which I would prefer).
From the source:
Ensure that the await in the FooAsync method doesn’t find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g.
int Sync() { return Task.Run(() => Library.FooAsync()).Result; }
FooAsync will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of FooAsync won’t be forced back to the thread that’s invoking Sync().
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.