a good article http://blog.stephencleary.com/2012/02/async-and-await.htmlhtml
https://www.cnblogs.com/ioexception/p/Async_Await_Asynchronous_Programming.htmlweb
In the overview, I mentioned that when you await a built-in awaitable, then the awaitable will capture the current 「context」 and later apply it to the remainder of the async method. What exactly is that 「context」?json
Simple answer:app
Complex answer:less
What does this mean in the real world? For one thing, capturing (and restoring) the UI/ASP.NET context is done transparently:async
// WinForms example (it works exactly the same for WPF). private async void DownloadFileButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the UI thread is not blocked by the file download. await DownloadFileAsync(fileNameTextBox.Text); // Since we resume on the UI context, we can directly access UI elements. resultTextBox.Text = "File downloaded!"; } // ASP.NET example protected async void MyButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the ASP.NET thread is not blocked by the file download. // This allows the thread to handle other requests while we're waiting. await DownloadFileAsync(...); // Since we resume on the ASP.NET context, we can access the current request. // We may actually be on another *thread*, but we have the same ASP.NET request context. Response.Write("File downloaded!"); }
This is great for event handlers, but it turns out to not be what you want for most other code (which is, really, most of the async code you’ll be writing).ide
Most of the time, you don’t need to sync back to the 「main」 context. Most async methods will be designed with composition in mind: they await other operations, and each one represents an asynchronous operation itself (which can be composed by others). In this case, you want to tell the awaiter to not capture the current context by calling ConfigureAwait and passing false, e.g.:post
private async Task DownloadFileAsync(string fileName) { // Use HttpClient or whatever to download the file contents. var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false); // Note that because of the ConfigureAwait(false), we are not on the original context here. // Instead, we're running on the thread pool. // Write the file contents out to a disk file. await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false); // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice. } // WinForms example (it works exactly the same for WPF). private async void DownloadFileButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the UI thread is not blocked by the file download. await DownloadFileAsync(fileNameTextBox.Text); // Since we resume on the UI context, we can directly access UI elements. resultTextBox.Text = "File downloaded!"; }
The important thing to note with this example is that each 「level」 of async method calls has its own context. DownloadFileButton_Click started in the UI context, and called DownloadFileAsync. DownloadFileAsync also started in the UI context, but then stepped out of its context by calling ConfigureAwait(false). The rest of DownloadFileAsync runs in the thread pool context. However, when DownloadFileAsync completes and DownloadFileButton_Click resumes, it does resume in the UI context.ui
A good rule of thumb is to use ConfigureAwait(false) unless you know you do need the context.this
So far, we’ve only considered serial composition: an async method waits for one operation at a time. It’s also possible to start several operations and await for one (or all) of them to complete. You can do this by starting the operations but not awaiting them until later:
public async Task DoOperationsConcurrentlyAsync() { Task[] tasks = new Task[3]; tasks[0] = DoOperation0Async(); tasks[1] = DoOperation1Async(); tasks[2] = DoOperation2Async(); // At this point, all three tasks are running at the same time. // Now, we await them all. await Task.WhenAll(tasks); } public async Task<int> GetFirstToRespondAsync() { // Call two web services; take the first response. Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() }; // Await for the first one to respond. Task<int> firstTask = await Task.WhenAny(tasks); // Return the result. return await firstTask; }
By using concurrent composition (Task.WhenAll or Task.WhenAny), you can perform simple concurrent operations. You can also use these methods along with Task.Run to do simple parallel computation. However, this is not a substitute for the Task Parallel Library - any advanced CPU-intensive parallel operations should be done with the TPL.
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
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 apply to any UI 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.
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 apply to any ASP.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.
Here’s the situation: remember from my intro post that after you await a Task, when the method continues it will continue in a context.
In the first case, this context is a UI context (which applies to any UI except 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 it does only allow one thread in at a time. This interesting aspect is not officially documented anywhere AFAIK, but it is mentioned in my MSDN article about SynchronizationContext.
So this is what happens, starting with the top-level method (Button1_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」.
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 does not resume 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.
Using ConfigureAwait(false)
to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false)
for every await
in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(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, but both must be applied to achieve maximum performance and responsiveness.