Async method deadlock problem Don't Block on Async Code (transfer), asyncblock
Today, when debugging the requet. GetRequestStreamAsync Asynchronous Method, the problem of not returning results may be a deadlock. I saw an article by a foreigner explaining the deadlock problem of the asynchronous method. The Lazy translation directly moved over.
Http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
This is a problem that is brought up repeatedly on the forums and Stack Overflow. I think it's the most-asked question by async newcomers once they 've learned the basics.
UI Example
Consider the example below. A button click will initiate a REST call and display the results in a text box (this sample is for Windows Forms, but the same principles applyAnyUI application ).
// My "library" method.public static async Task<JObject> GetJsonAsync(Uri uri){ using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri); return JObject.Parse(jsonString); }}// My "top-level" method.public void Button1_Click(...){ var jsonTask = GetJsonAsync(...); textBox1.Text = jsonTask.Result;}
The "GetJson" helper method takes care of making the actual REST call and parsing it as JSON. The button click handler waits for the helper method to complete and then displays its results.
This code will deadlock.
ASP. NET Example
This example is very similar; we have a library method that performs a REST call, only this time it's used in an ASP. NET context (Web API in this case, but the same principles applyAnyASP. NET application ):
// My "library" method.public static async Task<JObject> GetJsonAsync(Uri uri){ using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri); return JObject.Parse(jsonString); }}// My "top-level" method.public class MyController : ApiController{ public string Get() { var jsonTask = GetJsonAsync(...); return jsonTask.Result.ToString(); }}
This code will also deadlock. For the same reason.
What Causes the Deadlock
Here's the situation: remember from my intro post that after you await a Task, when the method continues it will continueIn a context.
In the first case, this context is a UI context (which appliesAnyUI handle T Console applications). In the second case, this context is an ASP. NET request context.
One other important point: an ASP. NET request context is not tied to a specific thread (like the UI context is), but itDoesOnly allow one thread inAt a time. This interesting aspect is not officially specified ented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.
So this is what happens, starting with the top-level method (button#click for UI/MyController. Get for ASP. NET ):
For the UI example, the "context" is the UI context; for the ASP. NET example, the "context" is the ASP. NET request context. this type of deadlock can be caused for either "context ".
Preventing the Deadlock
There are two best practices (both covered in my intro post) that avoid this situation:
Consider the first best practice. The new "library" method looks like this:
public static async Task<JObject> GetJsonAsync(Uri uri){ using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false); return JObject.Parse(jsonString); }}
This changes the continuation behavior of GetJsonAsync so that it doesNotResume on the context. Instead, GetJsonAsync will resume on a thread pool thread. This enables GetJsonAsync to complete the Task it returned without having to re-enter the context.
UsingConfigureAwait(false)
To avoid deadlocks is a dangerous practice. You wowould have to useConfigureAwait(false)
ForEvery await
In the transitive closure of all methods called by the blocking code,Including all third-and second-party code. UsingConfigureAwait(false)
To avoid deadlock is at best just a hack ).
As the title of this post points out, the better solution is "Don't block on async code ".
Consider the second best practice. The new "top-level" methods look like this:
public async void Button1_Click(...){ var json = await GetJsonAsync(...); textBox1.Text = json;}public class MyController : ApiController{ public async Task<string> Get() { var json = await GetJsonAsync(...); return json.ToString(); }}
This changes the blocking behavior of the top-level methods so that the context is never actually blocked; all "waits" are "asynchronous waits ".
Note:It is best to apply both best practices. Either one will prevent the deadlock,BothMust be applied to achieve maximum performance and responsiveness.
Resources
- My introduction to async/await is a good starting point.
- Stephen Toub's blog post Await, and UI, and deadlocks! Oh, my! Covers this exact type of deadlock (in January of 2011, no less !).
- If you prefer videos, Stephen Toub demoed this deadlock live (39: 40-42: 50, but the whole presentation is great !). Lucian Wischik also demoed this deadlock using VB ).
- The Async/Await FAQ goes into detail on exactly when contexts are captured and used for continuations.
This kind of deadlock is always the result of mixing synchronous with asynchronous code. usually this is because people are just trying out async with one small piece of code and use synchronous code everywhere else. unfortunately, partially-asynchronous code is much more complex and tricky than just making everything asynchronous.
If youDoNeed to maintain a partially-asynchronous code base, then be sure to check out two more of Stephen Toub's blog posts: Too Wrappers for Synchronous Methods and Synchronous Wrappers for Asynchronous Methods, as well as my AsyncEx library.