How to trigger (NOT avoid!) an HttpClient deadlock - c#

There are a number of questions on SO about how to avoid deadlocks in async code (for example, HttpClient methods) being called from sync code, like this. I'm aware of the various ways to avoid these deadlocks.
In contrast, I'd like to learn about strategies to aggravate or trigger these deadlocks in faulty code during testing.
Here's an example bit of bad code that recently caused problems for us:
public static string DeadlockingGet(Uri uri)
{
using (var http = new HttpClient())
{
var response = http.GetAsync(uri).Result;
response.EnsureSuccessStatusCode();
return response.Content.ReadAsStringAsync().Result;
}
}
It was being called from an ASP.NET app, and thus had a non-null value of SynchronizationContext.Current, which provided the fuel for a potential deadlock fire.
Aside from blatantly misusing HttpClient, this code deadlocked in one of our company's servers... but only sporadically.
My attempt to repro deadlock
I work in QA, so I tried to repro the deadlock via a unit test that hits a local instance of Fiddler's listener port:
public class DeadlockTest
{
[Test]
[TestCase("http://localhost:8888")]
public void GetTests(string uri)
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
var context = SynchronizationContext.Current;
var thread = Thread.CurrentThread.ManagedThreadId;
var result = DeadlockingGet(new Uri(uri));
var thread2 = Thread.CurrentThread.ManagedThreadId;
}
}
A couple things to note:
By default, a unit test has a null SynchronizationContext.Current, and so .Result captures the context of TaskScheduler, which is the thread pool context. Therefore I use SetSynchronizationContext to set it to a specific context, to more closely emulate what happens in an ASP.NET or UI context.
I've configured Fiddler to wait a while (~1 minute) before responding back. I've heard from coworkers that this may help repro the deadlock (but I have no hard evidence this is the case).
I've ran it with debugger to make sure that context is non-null and thread == thread2.
Unfortunately, I've had no luck triggering deadlocks with this unit test. It always finishes, no matter how long the delay in Fiddler is, unless the delay exceeds the 100-second default Timeout of HttpClient (in which case it just blows up with an exception).
Am I missing an ingredient to ignite a deadlock fire? I'd like to repro the deadlocks, just to be positive that our eventual fix actually works.

It seems you think that setting any synchronization context might cause deadlock with async code - that is not true. It is dangerous to block on async code in asp.net and UI applications because they have special, single, main thread. In UI applications that is, well, main UI thread, in ASP.NET applications there are many such threads, but for given request there is one - request thread.
Synchronization contexts of ASP.NET and UI applications are special in that they basically send callbacks to that one special thread. So when:
you execute some code on this thread
from that code you execute some async Task and block on it's Result.
That Task has await statement.
Deadlock will occur. Why this happens? Because continuation of async method is Posted to current synchronization context. Those special contexts we discuss above will send those continuations to the special main thread. You already execute code on this same thread and it is already blocked - hence deadlock.
So what are you doing wrong? First, SynchronizationContext is not special context we discussed above - it just posts continuations to thread pool thread. You need another one for tests. You can either use existing (like WindowsFormsSynchronizationContext), or create simple context which behaves the same (sample code, ONLY for demonstration purposes):
class QueueSynchronizationContext : SynchronizationContext {
private readonly BlockingCollection<Tuple<SendOrPostCallback, object>> _queue = new BlockingCollection<Tuple<SendOrPostCallback, object>>(new ConcurrentQueue<Tuple<SendOrPostCallback, object>>());
public QueueSynchronizationContext() {
new Thread(() =>
{
foreach (var item in _queue.GetConsumingEnumerable()) {
item.Item1(item.Item2);
}
}).Start();
}
public override void Post(SendOrPostCallback d, object state) {
_queue.Add(new Tuple<SendOrPostCallback, object>(d, state));
}
public override void Send(SendOrPostCallback d, object state) {
// Send should be synchronous, so we should block here, but we won't bother
// because for this question it does not matter
_queue.Add(new Tuple<SendOrPostCallback, object>(d, state));
}
}
All it does is puts all callbacks to single queue and executes them one by one on separate, single thread.
Simulating deadlock with this context is easy:
class Program {
static void Main(string[] args)
{
var ctx = new QueueSynchronizationContext();
ctx.Send((state) =>
{
// first, execute code on this context
// so imagine you are in ASP.NET request thread,
// or in WPF UI thread now
SynchronizationContext.SetSynchronizationContext(ctx);
Deadlock(new Uri("http://google.com"));
Console.WriteLine("No deadlock if got here");
}, null);
Console.ReadKey();
}
public static void NoDeadlock(Uri uri) {
DeadlockingGet(uri).ContinueWith(t =>
{
Console.WriteLine(t.Result);
});
}
public static string Deadlock(Uri uri)
{
// we are on "main" thread, doing blocking operation
return DeadlockingGet(uri).Result;
}
public static async Task<string> DeadlockingGet(Uri uri) {
using (var http = new HttpClient()) {
// await in async method
var response = await http.GetAsync(uri);
// this is continuation of async method
// it will be posted to our context (you can see in debugger), and will deadlock
response.EnsureSuccessStatusCode();
return response.Content.ReadAsStringAsync().Result;
}
}
}

You have not been able to reproduce the issue because SynchronizationContext itself does not mimic the context installed by ASP.NET. The base SynchronizationContext does no locking or synchronization, but the ASP.NET context does: Because HttpContext.Current is not thread-safe nor is it stored in the LogicalCallContext to be passed between threads, the AspNetSynchronizationContext does a bit of work to a. restore HttpContext.Current when resuming a task and b. lock to ensure that only one task is running for a given context.
A similar problem exists with MVC: http://btburnett.com/2016/04/testing-an-sdk-for-asyncawait-synchronizationcontext-deadlocks.html
The approach given there is to test your code with a context which ensures that Send or Post is never called on the context. If it is, this is an indication of the deadlocking behavior. To resolve, either make the method tree async all the way up or use ConfigureAwait(false) somewhere, which essentially detaches the task completion from the sync context. For more information, this article details when you should use ConfigureAwait(false)

Related

C# async/await with HttpClient GetAsync - non-async calling method

Here is an async/await when calling from a main function which cannot be marked async - the function must run synchronously, but because HttpClient is async, at some point, the await has to make things stop gracefully. I've read a lot about how .Result or .Wait can cause deadlocks, but those are the only versions that actually make the code synchronous.
Here is an example program, roughly following what the code does - note the 4 attempts in the loop - only one of them actually puts data out in the correct order. Is there something fundamentally wrong with this structure/can I not use example #3?
The closest example I can find is here and that is where the calling function can be made async, which this one cannot. I've tried making private static void Process() an async and calling it with Task.Run(async ()=> await Process()); but it still runs out of order. The only thing that consistently works is Wait/Result which can deadlock, particularly with HttpClient from what I've read. Any thoughts?
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestAsync
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);
// Call 1
Process();
Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);
// Call 2
Process();
Console.ReadKey();
}
private static void Process()
{
Console.WriteLine("2. Calling CallAsyncTest at {0:mm.ss.fff}", DateTime.Now);
for (int i = 1; i < 4; i++)
{
// Try 1 - doesn't work
//CallAsyncTest(i);
// Try 2 - doesn't work
//Task.Run(async () => await CallAsyncTest(i));
// Try 3 - but works not recommended
CallAsyncTest(i).Wait();
// Try 4 - doesn't work
//CallAsyncTest(i).ConfigureAwait(false); ;
}
}
private static async Task CallAsyncTest(int i)
{
Console.WriteLine("{0}. Calling await AsyncTest.Start at {1:mm.ss.fff}", i + 2, DateTime.Now);
var x = await AsyncTest.Start(i);
}
}
public class AsyncTest
{
public static async Task<string> Start(int i)
{
Console.WriteLine("{0}. Calling await Post<string> at {1:mm.ss.fff}", i + 3, DateTime.Now);
return await Post<string>(i);
}
private static async Task<T> Post<T>(int i)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (HttpClient httpClient = new HttpClient(new HttpClientHandler()))
{
using (HttpResponseMessage response = await httpClient.GetAsync("https://www.google.com"))
{
using (HttpContent content = response.Content)
{
string responseString = await content.ReadAsStringAsync();
Console.WriteLine("response");
}
}
}
return default(T);
}
}
but those are the only versions that actually make the code synchronous
Exactly, you shouldn't use it for that reason alone. You don't want to lock your main thread while you perform I/O or your application is in the most literal sense trash.
If you don't want to make your entire application async-aware for whatever reason, you can still use proper async patterns. Remember that await is nothing but syntactic sugar around what the Task class already provides you: a way to continue after a task is complete.
So instead of having your process function lock your main thread, you can set up a daisy chain of .ContinueWith() on your four calls (if they're supposed to run in sequence) or on a Task.WhenAll (if they're supposed to run in parallel) and return immediately. The continuation then takes care of updating your UI with the data received (if successful) or error information (if it failed).
the function must run synchronously
As others have noted, the best solutions are to go async all the way. But I'll assume that there's a good reason why this isn't possible.
at some point, the await has to make things stop gracefully.
At some point, your code will have to block on the asynchronous code, yes.
I've read a lot about how .Result or .Wait can cause deadlocks, but those are the only versions that actually make the code synchronous.
Right. Blocking on asynchronous code is the only way to make it synchronous.
It's not actually about Result or Wait per se - it's any kind of blocking. And there are some situations where blocking on asynchronous code will cause deadlocks. Specifically, when all of these conditions are true:
The asynchronous code captures a context. await does capture contexts by default.
The calling code blocks a thread in that context.
The context only allows one thread at a time. E.g., UI thread contexts or legacy ASP.NET request contexts only allow one thread at a time.
Console applications and ASP.NET Core applications do not have a context, so a deadlock will not happen in those scenarios.
The only thing that consistently works is Wait/Result which can deadlock... Any thoughts?
There are some hacks you can choose from to block on asynchronous code.
One of them is just to block directly. This works fine for Console / ASP.NET Core applications (because they don't have a context that would cause the deadlock). I recommend using GetAwaiter().GetResult() in this case to avoid exception wrappers that come when using Result / Wait():
CallAsyncTest(i).GetAwaiter().GetResult();
However, that approach will not work if the application is a UI app or legacy ASP.NET app. In that case, you can push the asynchronous work off to a thread pool thread and block on that. The thread pool thread runs outside the context and so it avoids the deadlock, but this hack means that CallAsyncTest must be able to be run on a thread pool thread - no accessing UI elements or HttpContext.Current or anything else that depends on the context, because the context won't be there:
Task.Run(() => CallAsyncTest(i)).GetAwaiter().GetResult();
There is no solution that works in every scenario. Blocking directly works if there isn't a context (e.g., Console apps). Blocking on Task.Run works if the code can run on an arbitrary thread pool thread. There are a few other hacks available but those two are the most common.
Here's why the other attempts failed.
This one starts the task but doesn't wait at all:
// Try 1 - doesn't work
CallAsyncTest(i);
This one starts the task in a thread pool thread but doesn't wait at all:
// Try 2 - doesn't work
Task.Run(async () => await CallAsyncTest(i));
"Try 4" is exactly the same as "Try 1". The call to ConfigureAwait does nothing because there is no await to configure. So it also just starts the task but doesn't wait at all:
// Try 4 - doesn't work
CallAsyncTest(i).ConfigureAwait(false);
Why not making the Process method async, and then use the normal async await pattern?
The Process signature would be
private static async Task Process()
That would allow you to use await CallAsyncTest(i)
and you would Wait it on the main method, like so:
Process.GetAwaiter().GetResult();
This would be the equivalent to the implementation of async main introduced in C# 7.1
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.1/async-main#detailed-design

KeyVaultClient hangs at GetSecretAsync [duplicate]

I have a multi-tier .Net 4.5 application calling a method using C#'s new async and await keywords that just hangs and I can't see why.
At the bottom I have an async method that extents our database utility OurDBConn (basically a wrapper for the underlying DBConnection and DBCommand objects):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Then I have a mid level async method that calls this to get some slow running totals:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Finally I have a UI method (an MVC action) that runs synchronously:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
The problem is that it hangs on that last line forever. It does the same thing if I call asyncTask.Wait(). If I run the slow SQL method directly it takes about 4 seconds.
The behaviour I'm expecting is that when it gets to asyncTask.Result, if it's not finished it should wait until it is, and once it is it should return the result.
If I step through with a debugger the SQL statement completes and the lambda function finishes, but the return result; line of GetTotalAsync is never reached.
Any idea what I'm doing wrong?
Any suggestions to where I need to investigate in order to fix this?
Could this be a deadlock somewhere, and if so is there any direct way to find it?
Yep, that's a deadlock all right. And a common mistake with the TPL, so don't feel bad.
When you write await foo, the runtime, by default, schedules the continuation of the function on the same SynchronizationContext that the method started on. In English, let's say you called your ExecuteAsync from the UI thread. Your query runs on the threadpool thread (because you called Task.Run), but you then await the result. This means that the runtime will schedule your "return result;" line to run back on the UI thread, rather than scheduling it back to the threadpool.
So how does this deadlock? Imagine you just have this code:
var task = dataSource.ExecuteAsync(_ => 42);
var result = task.Result;
So the first line kicks off the asynchronous work. The second line then blocks the UI thread. So when the runtime wants to run the "return result" line back on the UI thread, it can't do that until the Result completes. But of course, the Result can't be given until the return happens. Deadlock.
This illustrates a key rule of using the TPL: when you use .Result on a UI thread (or some other fancy sync context), you must be careful to ensure that nothing that Task is dependent upon is scheduled to the UI thread. Or else evilness happens.
So what do you do? Option #1 is use await everywhere, but as you said that's already not an option. Second option which is available for you is to simply stop using await. You can rewrite your two functions to:
public static Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
return Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
}
public static Task<ResultClass> GetTotalAsync( ... )
{
return this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
}
What's the difference? There's now no awaiting anywhere, so nothing being implicitly scheduled to the UI thread. For simple methods like these that have a single return, there's no point in doing an "var result = await...; return result" pattern; just remove the async modifier and pass the task object around directly. It's less overhead, if nothing else.
Option #3 is to specify that you don't want your awaits to schedule back to the UI thread, but just schedule to the thread pool. You do this with the ConfigureAwait method, like so:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var resultTask = this.DBConnection.ExecuteAsync<ResultClass>(
ds => return ds.Execute("select slow running data into result");
return await resultTask.ConfigureAwait(false);
}
Awaiting a task normally would schedule to the UI thread if you're on it; awaiting the result of ContinueAwait will ignore whatever context you are on, and always schedule to the threadpool. The downside of this is you have to sprinkle this everywhere in all functions your .Result depends on, because any missed .ConfigureAwait might be the cause of another deadlock.
This is the classic mixed-async deadlock scenario, as I describe on my blog. Jason described it well: by default, a "context" is saved at every await and used to continue the async method. This "context" is the current SynchronizationContext unless it it null, in which case it is the current TaskScheduler. When the async method attempts to continue, it first re-enters the captured "context" (in this case, an ASP.NET SynchronizationContext). The ASP.NET SynchronizationContext only permits one thread in the context at a time, and there is already a thread in the context - the thread blocked on Task.Result.
There are two guidelines that will avoid this deadlock:
Use async all the way down. You mention that you "can't" do this, but I'm not sure why not. ASP.NET MVC on .NET 4.5 can certainly support async actions, and it's not a difficult change to make.
Use ConfigureAwait(continueOnCapturedContext: false) as much as possible. This overrides the default behavior of resuming on the captured context.
I was in the same deadlock situation but in my case calling an async method from a sync method, what works for me was:
private static SiteMetadataCacheItem GetCachedItem()
{
TenantService TS = new TenantService(); // my service datacontext
var CachedItem = Task.Run(async ()=>
await TS.GetTenantDataAsync(TenantIdValue)
).Result; // dont deadlock anymore
}
is this a good approach, any idea?
Just to add to the accepted answer (not enough rep to comment), I had this issue arise when blocking using task.Result, event though every await below it had ConfigureAwait(false), as in this example:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
The issue actually lay with the external library code. The async library method tried to continue in the calling sync context, no matter how I configured the await, leading to deadlock.
Thus, the answer was to roll my own version of the external library code ExternalLibraryStringAsync, so that it would have the desired continuation properties.
wrong answer for historical purposes
After much pain and anguish, I found the solution buried in this blog post (Ctrl-f for 'deadlock'). It revolves around using task.ContinueWith, instead of the bare task.Result.
Previously deadlocking example:
public Foo GetFooSynchronous()
{
var foo = new Foo();
foo.Info = GetInfoAsync.Result; // often deadlocks in ASP.NET
return foo;
}
private async Task<string> GetInfoAsync()
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
Avoid the deadlock like this:
public Foo GetFooSynchronous
{
var foo = new Foo();
GetInfoAsync() // ContinueWith doesn't run until the task is complete
.ContinueWith(task => foo.Info = task.Result);
return foo;
}
private async Task<string> GetInfoAsync
{
return await ExternalLibraryStringAsync().ConfigureAwait(false);
}
quick answer :
change this line
ResultClass slowTotal = asyncTask.Result;
to
ResultClass slowTotal = await asyncTask;
why? you should not use .result to get the result of tasks inside most applications except console applications if you do so your program will hang when it gets there
you can also try the below code if you want to use .Result
ResultClass slowTotal = Task.Run(async ()=>await asyncTask).Result;

Avoid async from spreading in method signatures

What prevents me from constantly using the async/wait pattern in my .net code is that once you create an async method the async keyword tends to spread thru my code forcing me to make all methods async. Is there a pattern to stop this efficiently?
What prevents me from constantly using the async/wait pattern in my .net code is that once you create an async method the async keyword tends to spread thru my code forcing me to make all methods async. Is there a pattern to stop this efficiently?
Let me ask you this, then: why are you using async/await? You need to take a step back and decide if you want the benefits of asynchronous code. If you do want those benefits, then your code must be asynchronous. As soon as you block a thread, you lose all the benefits of async. So really, why use asynchronous code at all, if you're just going to block a thread anyway?
That said, there are some scenarios where a partially-async stack makes sense. For example, if your code base is temporarily in a transition state. In this case, you can use one of the hacks described in my article on brownfield async:
Blocking directly (may cause deadlocks).
Blocking on a thread pool thread (executes code on a different thread and different context, may cause unexpected parallelism).
Blocking on the current thread with a thread pool context (executes code in a different context, may cause unexpected parallelism).
Blocking on a thread with a single-threaded context (executes code on a different thread and different context).
Blocking on a nested message loop (may cause unexpected reentrancy).
All of these are hacks, and all have different drawbacks. There is no hack that works in every scenario.
As commented earlier you should go async all the way...
In some cases we use a litle helper class to cut off the async pattern. This class alows you to wait for the result without ending up with a deadlock by replacing the SynchronizationContext.
public class NoSynchronizationContextScope : IDisposable
{
private readonly SynchronizationContext synchronizationContext;
public NoSynchronizationContextScope()
{
synchronizationContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
}
public void Dispose() => SynchronizationContext.SetSynchronizationContext(synchronizationContext);
}
void fn()
{
using (new NoSynchronizationContextScope())
{
fnAsync().Wait();
// or
// var result = fnAsync().Result();
// in case you have to wait for a result
}
}
In the very particular case of a method that you want to start running but not wait for the result of (i.e. triggering an event handler) you can use async void. Just make sure you catch all the errors at that point:
public async void DoSomething() {
try {
var o = await DoThing1();
await DoThing2(o);
} catch (Exception e) {
// Log the error somewhere (to a file, database, etc.)
}
}
You can use async method and invoke not async method in task so outer method will behave like async and inner method is still sync.
public async Task OuterFuncAsync()
{
await Task.Run(() => InnerFunc());
}

async/await deadlocking when using a SynchronizationContext

According to this link:
When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. One of the purposes of this
action is to handle synchronization with the UI thread. The key
component of this feature is the SynchronizationContext.Current
which gets the synchronization context for the current thread.
SynchronizationContext.Current is populated depending on the
environment you are in. The GetAwaiter method of Task looks up for
SynchronizationContext.Current. If current synchronization context
is not null, the continuation that gets passed to that awaiter will
get posted back to that synchronization context.
When consuming a method, which uses the new asynchronous language features, in a blocking fashion, you will end up with a deadlock if
you have an available SynchronizationContext. When you are
consuming such methods in a blocking fashion (waiting on the Task
with Wait method or taking the result directly from the Result
property of the Task), you will block the main thread at the same
time. When eventually the Task completes inside that method in the
threadpool, it is going to invoke the continuation to post back to
the main thread because SynchronizationContext.Current is
available and captured. But there is a problem here: the UI thread
is blocked and you have a deadlock!
public class HomeController : Controller
{
public ViewResult CarsSync()
{
SampleAPIClient client = new SampleAPIClient();
var cars = client.GetCarsInAWrongWayAsync().Result;
return View("Index", model: cars);
}
}
public class SampleAPIClient
{
private const string ApiUri = "http://localhost:17257/api/cars";
public async Task<IEnumerable<Car>> GetCarsInAWrongWayAsync()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(ApiUri);
// Not the best way to handle it but will do the work for demo purposes
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<IEnumerable<Car>>();
}
}
}
I have trouble understanding the bolded part of the statement above, but when I test the code above, it deadlocks as expected.
But I still can't understand why the UI thread is blocked?
In this case, what is the available SynchronizationContext? Is it the UI thread?
I explain this in full in my own blog post, but to reiterate here...
await by default will capture a current "context" and resume its async method on that context. This context is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current.
Deadlocks can occur when you have a one-thread-at-a-time SynchronizationContext and you block on a task representing asynchronous code (e.g., using Task.Wait or Task<T>.Result). Note that it is the blocking that causes the deadlock, not just the SynchronizationContext; the appropriate resolution (almost always) is to make the calling code asynchronous (e.g., replace Task.Wait/Task<T>.Result with await). This is especially true on ASP.NET.
But I still can't understand why the UI thread is blocked?
Your example is running on ASP.NET; there is no UI thread.
what is the available SynchronizationContext?
The current SynchronizationContext should be an instance of AspNetSynchronizationContext, a context that represents an ASP.NET request. This context only allows one thread in at a time.
So, walking through your example:
When a request comes in for this action, CarsSync will start executing within that request context. It proceeds to this line:
var cars = client.GetCarsInAWrongWayAsync().Result;
which is essentially the same as this:
Task<IEnumerable<Car>> carsTask = client.GetCarsInAWrongWayAsync();
var cars = carsTask.Result;
So, it proceeds to call into GetCarsInAWrongWayAsync, which runs until it hits its first await (the GetAsync call). At this point, GetCarsInAWrongWayAsync captures its current context (the ASP.NET request context) and returns an incomplete Task<IEnumerable<Car>>. When the GetAsync download finishes, GetCarsInAWrongWayAsync will resume executing on that ASP.NET request context and (eventually) complete the task it already returned.
However, as soon as GetCarsInAWrongWayAsync returns the incomplete task, CarsSync blocks the current thread, waiting for that task to complete. Note that the current thread is in that ASP.NET request context, so CarsSync will prevent GetCarsInAWrongWayAsync from ever resuming execution, causing the deadlock.
As a final note, GetCarsInAWrongWayAsync is an OK method. It would be better if it used ConfigureAwait(false), but it's not actually wrong. CarsSync is the method causing the deadlock; it's call to Task<T>.Result is wrong. The appropriate fix is to change CarsSync:
public class HomeController : Controller
{
public async Task<ViewResult> CarsSync()
{
SampleAPIClient client = new SampleAPIClient();
var cars = await client.GetCarsInAWrongWayAsync();
return View("Index", model: cars);
}
}
The key point is that some SynchronizationContexts only allow a single thread to run code at the same time. One thread is calling Result or Wait. When the async methods wants to enter it can't.
Some SynchronizationContexts are mutli-threaded and the problem does not occur.

Different behavior when using ContinueWith or Async-Await

When I use an async-await method (as the example below) in a HttpClient call, this code causes a deadlock. Replacing the async-await method with a t.ContinueWith, it works properly. Why?
public class MyFilter: ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var user = _authService.GetUserAsync(username).Result;
}
}
public class AuthService: IAuthService {
public async Task<User> GetUserAsync (string username) {
var jsonUsr = await _httpClientWrp.GetStringAsync(url).ConfigureAwait(false);
return await JsonConvert.DeserializeObjectAsync<User>(jsonUsr);
}
}
This works:
public class HttpClientWrapper : IHttpClient {
public Task<string> GetStringAsync(string url) {
return _client.GetStringAsync(url).ContinueWith(t => {
_log.InfoFormat("Response: {0}", url);
return t.Result;
});
}
This code will deadlock:
public class HttpClientWrapper : IHttpClient {
public async Task<string> GetStringAsync(string url) {
string result = await _client.GetStringAsync(url);
_log.InfoFormat("Response: {0}", url);
return result;
}
}
I describe this deadlock behavior on my blog and in a recent MSDN article.
await will by default schedule its continuation to run inside the current SynchronizationContext, or (if there is no SynchronizationContext) the current TaskScheduler. (Which in this case is the ASP.NET request SynchronizationContext).
The ASP.NET SynchronizationContext represents the request context, and ASP.NET only allows one thread in that context at a time.
So, when the HTTP request completes, it attempts to enter the SynchronizationContext to run InfoFormat. However, there is already a thread in the SynchronizationContext - the one blocked on Result (waiting for the async method to complete).
On the other hand, the default behavior for ContinueWith by default will schedule its continuation to the current TaskScheduler (which in this case is the thread pool TaskScheduler).
As others have noted, it's best to use await "all the way", i.e., don't block on async code. Unfortunately, that's not an option in this case since MVC does not support asynchronous action filters (as a side note, please vote for this support here).
So, your options are to use ConfigureAwait(false) or to just use synchronous methods. In this case, I recommend synchronous methods. ConfigureAwait(false) only works if the Task it's applied to has not already completed, so I recommend that once you use ConfigureAwait(false), you should use it for every await in the method after that point (and in this case, in each method in the call stack). If ConfigureAwait(false) is being used for efficiency reasons, then that's fine (because it's technically optional). In this case, ConfigureAwait(false) would be necessary for correctness reasons, so IMO it creates a maintenance burden. Synchronous methods would be clearer.
An explanation on why your await deadlocks
Your first line:
var user = _authService.GetUserAsync(username).Result;
blocks that thread and the current context while it waits for the result of GetUserAsync.
When using await it attempts to run any remaining statements back on the original context after the task being waited on finishes, which causes deadlocks if the original context is blocked (which is is because of the .Result). It looks like you attempted to preempt this problem by using .ConfigureAwait(false) in GetUserAsync, however by the time that that await is in effect it's too late because another await is encountered first. The actual execution path looks like this:
_authService.GetUserAsync(username)
_httpClientWrp.GetStringAsync(url) // not actually awaiting yet (it needs a result before it can be awaited)
await _client.GetStringAsync(url) // here's the first await that takes effect
When _client.GetStringAsync finishes, the rest of the code can't continue on the original context because that context is blocked.
Why ContinueWith behaves differently
ContinueWith doesn't try to run the other block on the original context (unless you tell it to with an additional parameter) and thus does not suffer from this problem.
This is the difference in behavior that you noticed.
A solution with async
If you still want to use async instead of ContinueWith, you can add the .ConfigureAwait(false) to the first encountered async:
string result = await _client.GetStringAsync(url).ConfigureAwait(false);
which as you most likely already know, tells await not to try to run the remaining code on the original context.
Note for the future
Whenever possible, attempt to not use blocking methods when using async/await. See Preventing a deadlock when calling an async method without using await for avoiding this in the future.
Granted, my answer is only partial, but I'll go ahead with it anyway.
Your Task.ContinueWith(...) call does not specify the scheduler, therefore TaskScheduler.Current will be used - whatever that is at the time. Your await snippet, however, will run on the captured context when the awaited task completes, so the two bits of code may or may not produce similar behaviour - depending on the value of TaskScheduler.Current.
If, say, your first snippet is called from the UI code directly (in which case TaskScheduler.Current == TaskScheduler.Default, the continuation (logging code) will execute on the default TaskScheduler - that is, on the thread pool.
In the second snippet, however, the continuation (logging) will actually run on the UI thread regardless of whether you use ConfigureAwait(false) on the task returned by GetStringAsync, or not. ConfigureAwait(false) will only affect the execution of the code after the call to GetStringAsync is awaited.
Here's something else to illustrate this:
private async void Form1_Load(object sender, EventArgs e)
{
await this.Blah().ConfigureAwait(false);
// InvalidOperationException here.
this.Text = "Oh noes, I'm no longer on the UI thread.";
}
private async Task Blah()
{
await Task.Delay(1000);
this.Text = "Hi, I'm on the UI thread.";
}
The given code sets the Text within Blah() just fine, but it throws a cross-threading exception inside the continuation in the Load handler.
I found the other solutions posted here did not work for me on ASP .NET MVC 5, which still uses synchronous Action Filters. The posted solutions don't guarantee a new thread will be used, they just specify that the same thread does not HAVE to be used.
My solution is to use Task.Factory.StartNew() and specifying TaskCreationOptions.LongRunning in the method call. This ensures a new/different thread is always used, so you can be assured you will never get a deadlock.
So, using the OP example, the following is the solution that works for me:
public class MyFilter: ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
// var user = _authService.GetUserAsync(username).Result;
// Guarantees a new/different thread will be used to make the enclosed action
// avoiding deadlocking the calling thread
var user = Task.Factory.StartNew(
() => _authService.GetUserAsync(username).Result,
TaskCreationOptions.LongRunning).Result;
}
}

Categories

Resources