Async Call with Await in Httpclient Never Returns

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.

Why do HTTP requests never return with async await?

So judging by the fact that ConfigureAwait(false) helped with your issue, please, read these from Stephen Cleary's blog:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

The guy's pretty much an async expert (he wrote the Concurrency in C# Cookbook book), so whatever I can say he probably explains better. Basically you're blocking the ASP.NET thread somewhere, maybe not using await all the way but rather Wait, Result or GetResult(). You should be able to diagnoze the issue yourself using that blog.

What ConfigureAwait(false) does is it does not capture the current context, so the HTTP request gets performed (correctly) somewhere else than on the ASP.NET context, preventing a deadlock.

EDIT:

GetAwaiter().GetResult() is what's causing the issue, judging by your comment. If you changed that to await and the calling method to async you'd probably fix everything.

Since C# 7.0 and async Task Main() method support there's really no reason to block instead of using await in your application code, ever.

Async REST API call never returns, but other async operations work as expected

Don't mix async/await with blocking calls like .Result which can lead to deadlocks.

You can create an event and event handler as a workaround

public MainViewModel() {
_client = new HttpEndpoint(Url, User, Pass);
TriggerDoorCommand = new Command(async () => await ExecuteTriggerDoorCommand());
//Subscribe to event
GetData += GetDataHandler;
//Raise event
GetData(this, EventArgs.Empty);
}

private event EventHandler GetData = delegate { };

private async void GetDataHandler(object sender, EventArgs args) {
_currentDoorFunc = await GetCurrentDoorFunction();
}

private async Task<GPIOFunctions> GetCurrentDoorFunction() {
return await _client.GetGPIOFunction(DOOR_TOGGLE_PIN);
}

Reference Async/Await - Best Practices in Asynchronous Programming

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.

Async method to make an http call using HttpClient never comes back

The first example works because either the Task is in the .Completed == true state before you call .Result on it or that line of code has SyncronisationContext.Current == null.

If the task is not in the completed state calling .Result or .Wait() on a task can result in a deadlock if SyncronisationContext.Current != null at the point you called .Result or .Wait(). The best thing to do is mark the entire call stack as async and start returning Task instead of void and Task<T> where you return T then calling await on all places you used .Result or .Wait().

If you are not willing to make those code changes you must switch to using non async methods, this will requite switching from HttpClient to WebClient and using it's non async method calls to do your requests.

Trying to understand Async/Await but seem to get deadlock c#

I had to put await on the CalculateInvoice method for it to move on.

[HttpPost]
[Route("calculateInvoice")]
public async Task<IHttpActionResult> CalculateInvoice([FromBody] InvoiceRequestModel model)
{
model.EmailAddress = AccountHelper.GetLoggedInUsername();
var result = await _paymentHandler.CreatePaymentsAndSendAsEmail(model, true);
if (result == null)
return Conflict();
return Ok(result);
}


Related Topics



Leave a reply



Submit