Asynchronous code reminds me of the stories of a fellow who mentioned that the world is suspended in space and is Immediat Ely challenged by an elderly lady claiming, the world rested on the back of a giant turtle. When the man enquired what the turtle is standing on, the lady replied, "You ' re very clever, young man, but it's turtles All the down! " As you convert synchronous code to asynchronous code, you'll find the It works best if asynchronous code calls and is Cal Led by other asynchronous Code-all the "the" and "up" if you prefer. Others has also noticed the spreading behavior of asynchronous programming and has called it "contagious" or compared it. to a zombie virus. Whether turtles or zombies, it ' s definitely true that asynchronous code tends to drive surrounding code to also be ASYNCHR Onous. This behavior are inherent in all types of asynchronous programming, not just the new async/await keywords.
"Async all the" means, shouldn ' t mix synchronous and asynchronous code without carefully considering the CO Nsequences. In particular, it's usually a bad idea to block on async code by calling Task.wait or Task.result. This was an especially common problem for programmers who was "dipping their toes" into asynchronous programming, Convertin G Just a small part of their application and wrapping it on a synchronous API so the rest of the application is isolated F Rom the changes. Unfortunately, they run into the problems with deadlocks. After answering many async-related questions on the MSDN forums, Stack Overflow and e-mail, I can say this was by far the M Ost-asked Question by Async newcomers once they learn the basics: ' Why does my partially async code deadlock? '
Figure 3 shows a simple example where one method blocks on the result of an async method. This code would work just fine in a console application but would deadlock when called from a GUI or ASP. This behavior can is confusing, especially considering that stepping through the debugger implies that it's the await that Never completes. The actual cause of the deadlock is further the "Call stack" when task.wait is called.
Figure 3 A Common Deadlock problem if Blocking on Async Code
Public Static classdeadlockdemo{Private Static AsyncTask Delayasync () {awaitTask.delay ( +); } //This method causes a deadlock when the called in a GUI or ASP. Public Static voidTest () {//Start the delay. varDelaytask =Delayasync (); //Wait for the delay to complete.delaytask.wait (); }}
The root cause of this deadlock are due to the the-the-handles contexts. By default, when an incomplete Task was awaited, the current "context" was captured and used to resume the method when the T Ask completes. This "context" was the current SynchronizationContext unless it's null, in which case it's the current TaskScheduler. The GUI and ASP. Applications has a synchronizationcontext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already have a thread in it, which are (synchronously) waiting for the Async method to complete. They ' re each waiting to the other, causing a deadlock.
Note that the console applications don ' t cause this deadlock. They has a thread pool SynchronizationContext instead of a one-chunk-at-a-time SynchronizationContext, so when the await Completes, it schedules the remainder of the async method on a thread pool thread. The method is able to complete, which completes its returned task, and there's no deadlock. This difference in behavior can is confusing when programmers write a test console program, observe the partially async co De work as expected, and then move the same code into a GUI or ASP. Application, where it deadlocks.
The best solution to this problem are to allow Async code to grow naturally through the codebase. If you follow this solution, you'll see the async code expand to its entry point, the usually an event handler or controller Actio N. Console applications can ' t follow this solution fully because the Main method can ' t is async. If the Main method were async, it could return before it completed, causing the program to end. Figure 4 demonstrates this exception to the Guideline:the Main method for a console application is one of the FE W situations where code may block on an asynchronous method.
Figure 4 The Main Method could call task.wait or Task.result
class program{ static void Main () {Mainasync (). Wait (); static async Task Mainasync () { try { // asynchronous implementation. await Task.delay (1000 ); catch (Exception ex) { // Handle exceptions.
Allowing Async to grow through the codebase are the best solution, but this means there's a lot of initial work for a Pplication to see real benefit from Async code. There is a few techniques for incrementally converting a large codebase to async code, but they ' re outside the scope of T His article. In some cases, using task.wait or Task.result can help with a partial conversion, but you need to be aware of the deadlock Problem as well as the error-handling problem. I ' ll explain the error-handling problem now and show what to avoid the deadlock problem later in this article.
Every Task would store a list of exceptions. When you await a Task, the first exception are re-thrown, so can catch the specific exception type (such as Invalidoper ationexception). However, when synchronously block on a Task using task.wait or Task.result, all of the exceptions is wrapped in an Ag Gregateexception and thrown. Refer again to Figure 4. The Try/catch in Mainasync would catch a specific exception type, but if you put the Try/catch in Main and then it'll always Catch an aggregateexception. Error handling is much easier to deal with when you don't have a aggregateexception, so I put the "global" Try/catch in M Ainasync.
So far, I ' ve shown the problems with blocking on async code:possible deadlocks and more-complicated error handling. There ' s also a problem with using blocking code within an async method. Consider this simple example:
public static class notfullyasynchronousdemo{ // This method synchronously blocks a thread. public static async Task Testnotfullyasync () { await Task.yield (); Thread.Sleep ( 5000 ); }}
This method isn ' t fully asynchronous. It would immediately yield, returning an incomplete task, if it resumes it would synchronously block whatever thread I S running. If This method was called from a GUI context, it would block the GUI thread; If it's called from an ASP. Request context, it would block the current ASP. Asynchronous code works best if it doesn ' t synchronously block. Figure 5 are a cheat sheet of async replacements for synchronous operations.
Figure 5 The "Async" of Doing Things
To does this ... |
Instead of this ... |
Use this |
Retrieve The result of a background task |
Task.wait or Task.result |
Await |
Wait for any task to complete |
Task.waitany |
Await Task.whenany |
Retrieve the results of multiple tasks |
Task.waitall |
Await Task.whenall |
Wait a period of time |
Thread.Sleep |
Await Task.delay |
To summarize the second guideline, you should avoid mixing async and blocking code. Mixed Async and blocking code can cause deadlocks, more-complex error handling and unexpected blocking of the context threads. The exception to this guideline are the Main method for console applications, or-if your ' re an advanced user-managing a par tially asynchronous codebase.
Async all the