Httpclient.Getasync(...) Never Returns When Using Await/Async

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 executes AsyncAwait_GetSomeDataAsync (within the ASP.NET request context).
  • AsyncAwait_GetSomeDataAsync executes HttpClient.GetAsync (within the ASP.NET request context).
  • The HTTP request is sent out, and HttpClient.GetAsync returns an uncompleted Task.
  • AsyncAwait_GetSomeDataAsync awaits the Task; since it is not complete, AsyncAwait_GetSomeDataAsync returns an uncompleted Task.
  • Test5Controller.Get blocks the current thread until that Task completes.
  • The HTTP response comes in, and the Task returned by HttpClient.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 in Test5Controller.Get.
  • Deadlock.

Here's why the other ones work:

  • (test1, test2, and test3): Continuations_GetSomeDataAsync schedules the continuation to the thread pool, outside the ASP.NET request context. This allows the Task returned by Continuations_GetSomeDataAsync to complete without having to re-enter the request context.
  • (test4 and test6): Since the Task is awaited, the ASP.NET request thread is not blocked. This allows AsyncAwait_GetSomeDataAsync to use the ASP.NET request context when it is ready to continue.

And here's the best practices:

  1. In your "library" async methods, use ConfigureAwait(false) whenever possible. In your case, this would change AsyncAwait_GetSomeDataAsync to be var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Don't block on Tasks; it's async all the way down. In other words, use await instead of GetResult (Task.Result and Task.Wait should also be replaced with await).

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 how Task awaiters use SynchronizationContext.
  • 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):

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).

  2. GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).

  3. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.

  4. 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.

  5. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.

  6. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.

  7. 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.

  8. 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):

  1. In your “library” async methods, use ConfigureAwait(false) wherever possible.
  2. 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:

  1. Not calling GetInformationAsync in a sync way
  2. Postfixing your async calls in GetInformationAsync with ConfigureAwait(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



Leave a reply



Submit