Different behavior when using ContinueWith or Async-Await
.net, asp.net-mvc, async-await, c#, multithreading
Solution
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.
Problem
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; } } ```