diff --git a/docs/csharp/programming-guide/concepts/async/index.md b/docs/csharp/programming-guide/concepts/async/index.md index 8d799670430bf..a51145df58506 100644 --- a/docs/csharp/programming-guide/concepts/async/index.md +++ b/docs/csharp/programming-guide/concepts/async/index.md @@ -5,7 +5,7 @@ ms.date: 06/04/2020 --- # Asynchronous programming with async and await -The [Task asynchronous programming model (TAP)](task-asynchronous-programming-model.md) provides an abstraction over asynchronous code. You write code as a sequence of statements, just like always. You can read that code as though each statement completes before the next begins. The compiler performs a number of transformations because some of those statements may start work and return a that represents the ongoing work. +The [Task asynchronous programming model (TAP)](task-asynchronous-programming-model.md) provides an abstraction over asynchronous code. You write code as a sequence of statements, just like always. You can read that code as though each statement completes before the next begins. The compiler performs many transformations because some of those statements may start work and return a that represents the ongoing work. That's the goal of this syntax: enable code that reads like a sequence of statements, but executes in a much more complicated order based on external resource allocation and when tasks complete. It's analogous to how people give instructions for processes that include asynchronous tasks. Throughout this article, you'll use an example of instructions for making a breakfast to see how the `async` and `await` keywords make it easier to reason about code, that includes a series of asynchronous instructions. You'd write the instructions something like the following list to explain how to make a breakfast: @@ -39,7 +39,7 @@ If you want the computer to execute the above instructions asynchronously, you m These concerns are important for the programs you write today. When you write client programs, you want the UI to be responsive to user input. Your application shouldn't make a phone appear frozen while it's downloading data from the web. When you write server programs, you don't want threads blocked. Those threads could be serving other requests. Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. You pay for those blocked threads. -Successful modern applications require asynchronous code. Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. The advantage of the synchronous code is that it's step-by-step actions make it easy to scan and understand. Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code. +Successful modern applications require asynchronous code. Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. The advantage of the synchronous code is that its step-by-step actions make it easy to scan and understand. Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code. ## Don't block, await instead @@ -47,7 +47,7 @@ The preceding code demonstrates a bad practice: constructing synchronous code to Let's start by updating this code so that the thread doesn't block while tasks are running. The `await` keyword provides a non-blocking way to start a task, then continue execution when that task completes. A simple asynchronous version of the make a breakfast code would look like the following snippet: -:::code language="csharp" source="snippets/index/AsyncBreakfast-V2/Program.cs" id="SnippetMain"::: +:::code language="csharp" source="snippets/index/AsyncBreakfast-V2/Program.cs" ID="SnippetMain"::: > [!IMPORTANT] > The total elapsed time is roughly the same as the initial synchronous version. The code has yet to take advantage of some of the key features of asynchronous programming. @@ -119,7 +119,7 @@ Console.WriteLine("Breakfast is ready!"); :::image type="content" source="media/asynchronous-breakfast.png" alt-text="asynchronous breakfast"::: -The asynchronously prepared breakfast took roughly 20 minutes, this is because some tasks were able to run concurrently. +The asynchronously prepared breakfast took roughly 20 minutes, this time savings is because some tasks ran concurrently. The preceding code works better. You start all the asynchronous tasks at once. You await each task only when you need the results. The preceding code may be similar to code in a web application that makes requests of different microservices, then combines the results into a single page. You'll make all the requests immediately, then `await` all those tasks and compose the web page. @@ -132,14 +132,83 @@ The preceding code works better. You start all the asynchronous tasks at once. Y The preceding code showed you that you can use or objects to hold running tasks. You `await` each task before using its result. The next step is to create methods that represent the combination of other work. Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. You can represent that work with the following code: -:::code language="csharp" source="snippets/index/AsyncBreakfast-V3/Program.cs" id="SnippetComposeToastTask"::: +:::code language="csharp" source="snippets/index/AsyncBreakfast-V3/Program.cs" ID="SnippetComposeToastTask"::: The preceding method has the `async` modifier in its signature. That signals to the compiler that this method contains an `await` statement; it contains asynchronous operations. This method represents the task that toasts the bread, then adds butter and jam. This method returns a that represents the composition of those three operations. The main block of code now becomes: -:::code language="csharp" source="snippets/index/AsyncBreakfast-V3/Program.cs" id="SnippetMain"::: +:::code language="csharp" source="snippets/index/AsyncBreakfast-V3/Program.cs" ID="SnippetMain"::: The previous change illustrated an important technique for working with asynchronous code. You compose tasks by separating the operations into a new method that returns a task. You can choose when to await that task. You can start other tasks concurrently. +## Asynchronous exceptions + +Up to this point, you've implicitly assumed that all these tasks complete successfully. Asynchronous methods throw exceptions, just like their synchronous counterparts. Asynchronous support for exceptions and error handling strives for the same goals as asynchronous support in general: You should write code that reads like a series of synchronous statements. Tasks throw exceptions when they can't complete successfully. The client code can catch those exceptions when a started task is `awaited`. For example, let's assume that the toaster catches fire while making the toast. You can simulate that by modifying the `ToastBreadAsync` method to match the following code: + +```csharp +private static async Task ToastBreadAsync(int slices) +{ + for (int slice = 0; slice < slices; slice++) + { + Console.WriteLine("Putting a slice of bread in the toaster"); + } + Console.WriteLine("Start toasting..."); + await Task.Delay(2000); + Console.WriteLine("Fire! Toast is ruined!"); + throw new InvalidOperationException("The toaster is on fire"); + await Task.Delay(1000); + Console.WriteLine("Remove toast from toaster"); + + return new Toast(); +} +``` + +> [!NOTE] +> You'll get a warning when you compile the preceding code regarding unreachable code. That's intentional, because once the toaster catches fire, operations won't proceed normally. + +Run the application after making these changes, and you'll output similar to the following text: + +```console +Pouring coffee +coffee is ready +Warming the egg pan... +putting 3 slices of bacon in the pan +cooking first side of bacon... +Putting a slice of bread in the toaster +Putting a slice of bread in the toaster +Start toasting... +Fire! Toast is ruined! +flipping a slice of bacon +flipping a slice of bacon +flipping a slice of bacon +cooking the second side of bacon... +cracking 2 eggs +cooking the eggs ... +Put bacon on plate +Put eggs on plate +eggs are ready +bacon is ready +Unhandled exception. System.InvalidOperationException: The toaster is on fire + at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.cs:line 65 + at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.cs:line 36 + at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24 + at AsyncBreakfast.Program.
(String[] args) +``` + +Notice that there's quite a few tasks completing between when the toaster catches fire and the exception is observed. When a task that runs asynchronously throws an exception, that Task is ***faulted***. The Task object holds the exception thrown in the property. Faulted tasks throw an exception when they're awaited. + +There are two important mechanisms to understand: how an exception is stored in a faulted task, and how an exception is unpackaged and rethrown when code awaits a faulted task. + +When code running asynchronously throws an exception, that exception is stored in the `Task`. The property is an because more than one exception may be thrown during asynchronous work. Any exception thrown is added to the collection. If that `Exception` property is null, a new `AggregateException` is created and the thrown exception is the first item in the collection. + +The most common scenario for a faulted task is that the `Exception` property contains exactly one exception. When code `awaits` a faulted task, the first exception in the collection is rethrown. That's why the output from this example shows an `InvalidOperationException` instead of an `AggregateException`. Extracting the first inner exception makes working with asynchronous methods as similar as possible to working with their synchronous counterparts. You can examine the `Exception` property in your code when your scenario may generate multiple exceptions. + +Before going on, comment out these two lines in your `ToastBreadAsync` method. You don't want to start another fire: + +```csharp +Console.WriteLine("Fire! Toast is ruined!"); +throw new InvalidOperationException("The toaster is on fire"); +``` + ## Await tasks efficiently The series of `await` statements at the end of the preceding code can be improved by using methods of the `Task` class. One of those APIs is , which returns a that completes when all the tasks in its argument list have completed, as shown in the following code: @@ -181,7 +250,7 @@ After all those changes, the final version of the code looks like this: :::image type="content" source="media/whenany-async-breakfast.png" alt-text="when any async breakfast"::: -The final version of the asynchronously prepared breakfast took roughly 15 minutes, this is because some tasks were able to run concurrently, and the code was able to monitor multiple tasks at once and only take action when it was needed. +The final version of the asynchronously prepared breakfast took roughly 15 minutes because some tasks ran concurrently, and the code monitored multiple tasks at once and only take action when it was needed. This final code is asynchronous. It more accurately reflects how a person would cook a breakfast. Compare the preceding code with the first code sample in this article. The core actions are still clear from reading the code. You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. The language features for `async` and `await` provide the translation every person makes to follow those written instructions: start tasks as you can and don't block waiting for tasks to complete. diff --git a/docs/csharp/programming-guide/concepts/async/snippets/index/AsyncBreakfast-V3/Program.cs b/docs/csharp/programming-guide/concepts/async/snippets/index/AsyncBreakfast-V3/Program.cs index 321058d8db3de..55686db83691e 100644 --- a/docs/csharp/programming-guide/concepts/async/snippets/index/AsyncBreakfast-V3/Program.cs +++ b/docs/csharp/programming-guide/concepts/async/snippets/index/AsyncBreakfast-V3/Program.cs @@ -60,7 +60,11 @@ private static async Task ToastBreadAsync(int slices) Console.WriteLine("Putting a slice of bread in the toaster"); } Console.WriteLine("Start toasting..."); - await Task.Delay(3000); + await Task.Delay(2000); + // Uncomment these lines for testing exception behavior. + // Console.WriteLine("Fire! Toast is ruined!"); + // throw new InvalidOperationException("The toaster is on fire"); + await Task.Delay(1000); Console.WriteLine("Remove toast from toaster"); return new Toast();