By Benjamin Fox


2012-04-27 01:28:07 8 Comments

Edit: This question looks like it might be the same problem, but has no responses...

Edit: In test case 5 the task appears to be stuck in WaitingForActivation state.

I've encountered some odd behaviour using the System.Net.Http.HttpClient in .NET 4.5 - where "awaiting" the result of a call to (e.g.) httpClient.GetAsync(...) will never return.

This only occurs in certain circumstances when using the new async/await language functionality and Tasks API - the code always seems to work when using only continuations.

Here's some code which reproduces the problem - drop this into a new "MVC 4 WebApi project" in Visual Studio 11 to expose the following GET endpoints:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

Each of the endpoints here return the same data (the response headers from stackoverflow.com) except for /api/test5 which never completes.

Have I encountered a bug in the HttpClient class, or am I misusing the API in some way?

Code to reproduce:

public class BaseApiController : ApiController
{
    /// <summary>
    /// Retrieves data using continuations
    /// </summary>
    protected Task<string> Continuations_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
    }

    /// <summary>
    /// Retrieves data using async/await
    /// </summary>
    protected async Task<string> AsyncAwait_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return result.Content.Headers.ToString();
    }
}

public class Test1Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await Continuations_GetSomeDataAsync();

        return data;
    }
}

public class Test2Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = Continuations_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test3Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return Continuations_GetSomeDataAsync();
    }
}

public class Test4Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await AsyncAwait_GetSomeDataAsync();

        return data;
    }
}

public class Test5Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = AsyncAwait_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test6Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return AsyncAwait_GetSomeDataAsync();
    }
}

6 comments

@Bondolin 2019-05-21 19:05:27

I'm going to put this in here more for completeness than direct relevance to the OP. I spent nearly a day debugging an HttpClient request, wondering why I was never getting back a response.

Finally found that I had forgotten to await the async call further down the call stack.

Feels about as good as missing a semicolon.

@Hasan Fathi 2018-01-20 15:12:52

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 .

@Ykok 2013-11-01 13:23:52

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

@Stephen Cleary 2013-11-28 19:15:22

Might want to re-read your source link; the author recommends not doing this. Does it work? Yes, but only in the sense that you avoid deadlock. This solution negates all the benefits of async code on ASP.NET, and in fact can cause problems at scale. BTW, ConfigureAwait does not "break proper async behavior" in any scenario; it's exactly what you should use in library code.

@Ykok 2013-11-29 21:02:41

I re-read my source link. Could not find what you were referring to. But if the chapter "Real-World Example" is mis-read it could sound like he is recommending not to do this, but he is in fact taking about a completely different case.

@Stephen Cleary 2013-11-29 21:24:33

It's the entire first section, titled in bold Avoid Exposing Synchronous Wrappers for Asynchronous Implementations. The entire rest of the post is explaining a few different ways to do it if you absolutely need to.

@Ykok 2013-12-05 13:22:22

Added the section I found in the source - I'll leave it up to future readers to decide. Note that you should generally try to avoid doing this and only do it as a last ditch resort (ie. when using async code you do not have control over).

@samneric 2014-05-08 02:20:27

I like all the answers here and as always.... they are all based on context (pun intended lol). I am wrapping HttpClient's Async calls with a synchronous version so I can't change that code to add ConfigureAwait to that library. So to prevent the deadlocks in production, I am wrapping the Async calls in a Task.Run. So as I understand it, this is going to use 1 extra thread per request and avoids the deadlock. I assume that to be completely compliant, I need to use WebClient's sync methods. That is a lot of work to justify so I'll need a compelling reason not stick with my current approach.

@samneric 2014-05-08 02:20:52

Incidentally, the reason I need to sync over async is because I need to call a web service within an already async controller method and it was calling it async was causing "async returned while async was waiting" issues. Maybe there is a fix to that problem that I now have time to address :)

@Don Cheadle 2017-05-16 22:46:40

@samneric - so you went with wrapping HttpClient's Async calls with Task.Run(() => ...).Result, correct? I'm in a similar situation. How'd that end up working out for you?

@samneric 2017-05-19 20:01:41

I ended up creating an Extension Method to convert Async to Sync. I read on here somewhere its the same way the .Net framework does it: public static TResult RunSync<TResult>(this Func<Task<TResult>> func) { return _taskFactory .StartNew(func) .Unwrap() .GetAwaiter() .GetResult(); }

@alex.peter 2017-02-08 08:19:04

These two schools are not really excluding.

Here is the scenario where you simply have to use

   Task.Run(() => AsyncOperation()).Wait(); 

or something like

   AsyncContext.Run(AsyncOperation);

I have a MVC action that is under database transaction attribute. The idea was (probably) to roll back everything done in the action if something goes wrong. This does not allow context switching, otherwise transaction rollback or commit is going to fail itself.

The library I need is async as it is expected to run async.

The only option. Run it as a normal sync call.

I am just saying to each its own.

@Don Cheadle 2017-05-16 22:44:39

so you're suggesting the first option in your answer?

@Stephen Cleary 2012-04-27 13:20:20

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:

Update 2012-07-13: Incorporated this answer into a blog post.

@svick 2012-04-27 13:27:05

Is there some documentation for the ASP.NET SynchroniztaionContext that explains that there can be only one thread in the context for some request? If not, I think there should be.

@Stephen Cleary 2012-04-27 13:50:52

It is not documented anywhere AFAIK.

@Benjamin Fox 2012-04-28 01:46:00

Thanks - awesome response. The difference in behaviour between (apparently) functionally identical code is frustrating but makes sense with your explanation. It would be useful if the framework was able to detect such deadlocks and raise an exception somewhere.

@AlexGad 2012-05-09 16:50:57

Are there situations where using .ConfigureAwait(false) in an asp.net context is NOT recommended? It would seem to me that it should always be used and that its only in a UI context that it should not be used since you need to sync to the UI. Or am I missing the point?

@Stephen Cleary 2012-05-10 01:56:08

The ASP.NET SynchronizationContext does provide some important functionality: it flows the request context. This includes all kinds of stuff from authentication to cookies to culture. So in ASP.NET, instead of syncing back to the UI, you sync back to the request context. This may change shortly: the new ApiController does have an HttpRequestMessage context as a property - so it may not be required to flow the context through SynchronizationContext - but I don't yet know.

@Mikael Eliasson 2013-05-23 07:41:19

Thank you! I misstakenly used Task.WaitAll instead of await Task.WhenAll. You answer put me on the right track

@Bart Calixto 2015-03-12 23:06:18

in your intro, you have public Task MyOldTaskParallelLibraryCode() {} (Task without async) what do you return here? because compiler wants you to return a Task. how do you create an empty task ?

@Stephen Cleary 2015-03-12 23:09:50

@Bart: Old-style TPL code generally uses Task.Factory.StartNew or TaskCompletionSource. Note that StartNew is not recommended for most modern code.

@Bart Calixto 2015-03-12 23:11:39

@StephenCleary im not familiar with old-style (nor new to be honest). I'm using new style, and I want a task that's CPU intensive to be run async but doesn't have any await methods inside. (it's more like a stream custom parser). What do you suggest me to return ?

@Stephen Cleary 2015-03-12 23:56:37

@Bart: Synchronous methods should have a synchronous API. If you want to call it asynchronously (i.e., to keep UI responsive), then you can call it using Task.Run. I have a Task.Run etiquette blog series that goes into detail.

@Bart Calixto 2015-03-13 02:43:09

@StephenCleary almost fall into the trap of wraping a sync method for offload. Not only it make sense and it's more clear when and how to use async, it also makes code easier!! I would +100 if I could.

@Don Cheadle 2017-05-17 01:09:44

@StephenCleary - so then why does it not cause a deadlock if you call response.GetStringAsAsync().Result in the middle of a controller? Basically, why then is there not a dead any/every time you call .Result on a .NET object inside of a .NET Web App? Seems like it should be the same problem - accessing it in a UI context, where an await will happen somewhere in a lib.

@Don Cheadle 2017-05-17 03:22:44

@StephenCleary - so is the main problem the use of await within a .Result inside of the same context? I've not been able to reproduce this kind of problem in a console application.

@Stephen Cleary 2017-05-17 13:36:55

@mmcrae: The deadlock is caused by the presence of a captured context (e.g., SynchronizationContext) and blocking the context on that task. Console apps do not have a SynchronizationContext. Similarly, GetStringAsAsync() does not capture the context (on that platform).

@Don Cheadle 2017-05-17 13:50:56

@StephenCleary - Is there some documentation that indicates the method won't capture/block on the UI context? Seems odd that some will, some won't. (msdn.microsoft.com/en-us/library/… is the actual method I meant to bring up)

@Stephen Cleary 2017-05-17 13:52:43

@mmcrae: MS APIs try not to capture contexts. Sometimes they do by mistake (it's an easy bug to have slip in). I'm not aware of any documentation for it, and you shouldn't depend on it - it's an implementation detail.

@Don Cheadle 2017-05-17 14:02:59

@StephenCleary - then I figure this is why you suggest await all the way down, as it's the safest from implementation changes etc. And if we really need to block during a request, should do Task.Run(() => ... which would put it into the thread pool.

@Stephen Cleary 2017-05-17 14:05:04

@mmcrae: Yes, that's one reason to go async all the way. If you do need to block, then it's best to use synchronous APIs all the way, e.g., WebClient. They were made for blocking.

@yamen 2012-04-27 01:49:14

I'm looking here:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx

And here:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

And seeing:

This type and its members are intended for use by the compiler.

Considering the await version works, and is the 'right' way of doing things, do you really need an answer to this question?

My vote is: Misusing the API.

@Benjamin Fox 2012-04-27 02:15:00

I hadn't noticed that, though I have seen other language around which indicates that using the GetResult() API is a supported (and expected) use-case.

@Benjamin Fox 2012-04-27 02:16:47

Further to that, if you refactor Test5Controller.Get() to eliminate the awaiter with the following: var task = AsyncAwait_GetSomeDataAsync(); return task.Result; The same behaviour can be observed.

Related Questions

Sponsored Content

35 Answered Questions

[SOLVED] How do I return the response from an asynchronous call?

8 Answered Questions

[SOLVED] How to safely call an async method in C# without await

24 Answered Questions

[SOLVED] How would I run an async Task<T> method synchronously?

3 Answered Questions

[SOLVED] Async call with await in HttpClient never returns

21 Answered Questions

[SOLVED] How and when to use ‘async’ and ‘await’

14 Answered Questions

[SOLVED] Using async/await with a forEach loop

6 Answered Questions

[SOLVED] async/await - when to return a Task vs void?

5 Answered Questions

[SOLVED] Using async/await for multiple tasks

2 Answered Questions

[SOLVED] When correctly use Task.Run and when just async-await

2 Answered Questions

Sponsored Content