From 82edd51cde5c1bcacce92bda6bfdac84246d5430 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:30:27 +0000 Subject: [PATCH 1/4] Initial plan From 05a0120da0a92ade3bb4372d73613ae0d74d7f6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:58:08 +0000 Subject: [PATCH 2/4] Add ContinueWith vs async/await comparison section Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- docs/csharp/asynchronous-programming/index.md | 30 ++++ .../AsyncBreakfast-ContinueWith.csproj | 8 + .../AsyncBreakfast-ContinueWith/Program.cs | 139 ++++++++++++++++++ .../ContinueWith-comparison.csproj | 8 + .../index/ContinueWith-comparison/Program.cs | 110 ++++++++++++++ 5 files changed, 295 insertions(+) create mode 100644 docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj create mode 100644 docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs create mode 100644 docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj create mode 100644 docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs diff --git a/docs/csharp/asynchronous-programming/index.md b/docs/csharp/asynchronous-programming/index.md index 4879d49130072..329ebae7feeeb 100644 --- a/docs/csharp/asynchronous-programming/index.md +++ b/docs/csharp/asynchronous-programming/index.md @@ -263,6 +263,36 @@ The code completes the asynchronous breakfast tasks in about 15 minutes. The tot The final code is asynchronous. It more accurately reflects how a person might cook breakfast. Compare the final code with the first code sample in the article. The core actions are still clear by reading the code. You can read the final code the same way you read the list of instructions for making a breakfast, as shown at the beginning of the article. The language features for the `async` and `await` keywords provide the translation every person makes to follow the written instructions: Start tasks as you can and don't block while waiting for tasks to complete. +## Async/await vs ContinueWith + +The `async` and `await` keywords provide syntactic simplification over using directly. At the language level, `async`/`await` is essentially syntactic sugar that the compiler transforms into continuation-based code using `ContinueWith`. However, this transformation provides significant readability and maintainability benefits, especially when chaining multiple asynchronous operations. + +Consider a scenario where you need to perform multiple sequential asynchronous operations. Here's how the same logic looks when implemented with `ContinueWith` compared to `async`/`await`: + +### Using ContinueWith + +With `ContinueWith`, each step in a sequence of asynchronous operations requires nested continuations: + +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="14-36"::: + +### Using async/await + +The same sequence of operations using `async`/`await` reads much more naturally: + +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="39-54"::: + +### Why async/await is preferred + +The `async`/`await` approach offers several advantages: + +- **Readability**: The code reads like synchronous code, making it easier to understand the flow of operations. +- **Maintainability**: Adding or removing steps in the sequence requires minimal code changes. +- **Error handling**: Exception handling with `try`/`catch` blocks works naturally, whereas `ContinueWith` requires careful handling of faulted tasks. +- **Debugging**: The call stack and debugger experience is much better with `async`/`await`. +- **Performance**: The compiler optimizations for `async`/`await` are more sophisticated than manual `ContinueWith` chains. + +The benefit becomes even more apparent as the number of chained operations increases. While a single continuation might be manageable with `ContinueWith`, sequences of 3-4 or more asynchronous operations quickly become difficult to read and maintain. This pattern, known as "monadic do-notation" in functional programming, allows you to compose multiple asynchronous operations in a sequential, readable manner. + ## Next step > [!div class="nextstepaction"] diff --git a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj new file mode 100644 index 0000000000000..d50c0e10aeba3 --- /dev/null +++ b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj @@ -0,0 +1,8 @@ + + + + Exe + net8.0 + + + \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs new file mode 100644 index 0000000000000..c9491701d2a38 --- /dev/null +++ b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace AsyncBreakfastContinueWith +{ + // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose. + internal class Bacon { } + internal class Coffee { } + internal class Egg { } + internal class Juice { } + internal class Toast { } + + class Program + { + static Task Main(string[] args) + { + Coffee cup = PourCoffee(); + Console.WriteLine("coffee is ready"); + + var eggsTask = FryEggsAsync(2); + var baconTask = FryBaconAsync(3); + var toastTask = MakeToastWithButterAndJamUsingContinueWith(2); + + var breakfastTasks = new List { eggsTask, baconTask, toastTask }; + return ProcessTasksUsingContinueWith(breakfastTasks, eggsTask, baconTask, toastTask) + .ContinueWith(_ => + { + Juice oj = PourOJ(); + Console.WriteLine("oj is ready"); + Console.WriteLine("Breakfast is ready!"); + }); + } + + static Task ProcessTasksUsingContinueWith(List breakfastTasks, Task eggsTask, Task baconTask, Task toastTask) + { + return Task.WhenAny(breakfastTasks) + .ContinueWith(finishedTaskContainer => + { + var finishedTask = finishedTaskContainer.Result; + if (finishedTask == eggsTask) + { + Console.WriteLine("eggs are ready"); + } + else if (finishedTask == baconTask) + { + Console.WriteLine("bacon is ready"); + } + else if (finishedTask == toastTask) + { + Console.WriteLine("toast is ready"); + } + + breakfastTasks.Remove(finishedTask); + + // Continue processing remaining tasks if any + if (breakfastTasks.Count > 0) + { + return ProcessTasksUsingContinueWith(breakfastTasks, eggsTask, baconTask, toastTask); + } + else + { + return Task.CompletedTask; + } + }).Unwrap(); + } + + static Task MakeToastWithButterAndJamUsingContinueWith(int number) + { + return ToastBreadAsync(number) + .ContinueWith(toastTask => + { + var toast = toastTask.Result; + ApplyButter(toast); + ApplyJam(toast); + return toast; + }); + } + + private static Juice PourOJ() + { + Console.WriteLine("Pouring orange juice"); + return new Juice(); + } + + private static void ApplyJam(Toast toast) => + Console.WriteLine("Putting jam on the toast"); + + private static void ApplyButter(Toast toast) => + Console.WriteLine("Putting butter on the toast"); + + 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(3000); + Console.WriteLine("Remove toast from toaster"); + + return new Toast(); + } + + private static async Task FryBaconAsync(int slices) + { + Console.WriteLine($"putting {slices} slices of bacon in the pan"); + Console.WriteLine("cooking first side of bacon..."); + await Task.Delay(3000); + for (int slice = 0; slice < slices; slice++) + { + Console.WriteLine("flipping a slice of bacon"); + } + Console.WriteLine("cooking the second side of bacon..."); + await Task.Delay(3000); + Console.WriteLine("Put bacon on plate"); + + return new Bacon(); + } + + private static async Task FryEggsAsync(int howMany) + { + Console.WriteLine("Warming the egg pan..."); + await Task.Delay(3000); + Console.WriteLine($"cracking {howMany} eggs"); + Console.WriteLine("cooking the eggs ..."); + await Task.Delay(3000); + Console.WriteLine("Put eggs on plate"); + + return new Egg(); + } + + private static Coffee PourCoffee() + { + Console.WriteLine("Pouring coffee"); + return new Coffee(); + } + } +} \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj new file mode 100644 index 0000000000000..d50c0e10aeba3 --- /dev/null +++ b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj @@ -0,0 +1,8 @@ + + + + Exe + net8.0 + + + \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs new file mode 100644 index 0000000000000..2550e1e0385ec --- /dev/null +++ b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs @@ -0,0 +1,110 @@ +using System; +using System.Threading.Tasks; + +namespace ContinueWithComparison +{ + class Program + { + static async Task Main(string[] args) + { + Console.WriteLine("=== ContinueWith approach ==="); + await MakeBreakfastWithContinueWith(); + + Console.WriteLine("\n=== async/await approach ==="); + await MakeBreakfastWithAsyncAwait(); + } + + // Using ContinueWith - demonstrates the complexity when chaining operations + static Task MakeBreakfastWithContinueWith() + { + return StartCookingEggsAsync() + .ContinueWith(eggsTask => + { + var eggs = eggsTask.Result; + Console.WriteLine("Eggs ready, starting bacon..."); + return StartCookingBaconAsync(); + }) + .Unwrap() + .ContinueWith(baconTask => + { + var bacon = baconTask.Result; + Console.WriteLine("Bacon ready, starting toast..."); + return StartToastingBreadAsync(); + }) + .Unwrap() + .ContinueWith(toastTask => + { + var toast = toastTask.Result; + Console.WriteLine("Toast ready, applying butter..."); + return ApplyButterAsync(toast); + }) + .Unwrap() + .ContinueWith(butteredToastTask => + { + var butteredToast = butteredToastTask.Result; + Console.WriteLine("Butter applied, applying jam..."); + return ApplyJamAsync(butteredToast); + }) + .Unwrap() + .ContinueWith(finalToastTask => + { + var finalToast = finalToastTask.Result; + Console.WriteLine("Breakfast completed with ContinueWith!"); + }); + } + + // Using async/await - much cleaner and easier to read + static async Task MakeBreakfastWithAsyncAwait() + { + var eggs = await StartCookingEggsAsync(); + Console.WriteLine("Eggs ready, starting bacon..."); + + var bacon = await StartCookingBaconAsync(); + Console.WriteLine("Bacon ready, starting toast..."); + + var toast = await StartToastingBreadAsync(); + Console.WriteLine("Toast ready, applying butter..."); + + var butteredToast = await ApplyButterAsync(toast); + Console.WriteLine("Butter applied, applying jam..."); + + var finalToast = await ApplyJamAsync(butteredToast); + Console.WriteLine("Breakfast completed with async/await!"); + } + + static async Task StartCookingEggsAsync() + { + Console.WriteLine("Starting to cook eggs..."); + await Task.Delay(1000); + return new { Item = "Eggs" }; + } + + static async Task StartCookingBaconAsync() + { + Console.WriteLine("Starting to cook bacon..."); + await Task.Delay(1000); + return new { Item = "Bacon" }; + } + + static async Task StartToastingBreadAsync() + { + Console.WriteLine("Starting to toast bread..."); + await Task.Delay(1000); + return new { Item = "Toast" }; + } + + static async Task ApplyButterAsync(object toast) + { + Console.WriteLine("Applying butter..."); + await Task.Delay(500); + return new { Item = "Buttered Toast" }; + } + + static async Task ApplyJamAsync(object butteredToast) + { + Console.WriteLine("Applying jam..."); + await Task.Delay(500); + return new { Item = "Completed Toast" }; + } + } +} \ No newline at end of file From 17ec3e8e0f17c1b4214890c7f5a17ae481435fa4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:00:31 +0000 Subject: [PATCH 3/4] Fix code snippet ranges for ContinueWith comparison Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- docs/csharp/asynchronous-programming/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/asynchronous-programming/index.md b/docs/csharp/asynchronous-programming/index.md index 329ebae7feeeb..2ae6ea45b10d4 100644 --- a/docs/csharp/asynchronous-programming/index.md +++ b/docs/csharp/asynchronous-programming/index.md @@ -273,13 +273,13 @@ Consider a scenario where you need to perform multiple sequential asynchronous o With `ContinueWith`, each step in a sequence of asynchronous operations requires nested continuations: -:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="14-36"::: +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="18-54"::: ### Using async/await The same sequence of operations using `async`/`await` reads much more naturally: -:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="39-54"::: +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="57-73"::: ### Why async/await is preferred From 3285a7249e93b6332e53394a1d0f6d51166c83ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:47:11 +0000 Subject: [PATCH 4/4] Address review comments: update .NET target, remove unused files, fix misleading text, use tag-based snippets Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- docs/csharp/asynchronous-programming/index.md | 6 +- .../AsyncBreakfast-ContinueWith.csproj | 8 - .../AsyncBreakfast-ContinueWith/Program.cs | 139 ------------------ .../ContinueWith-comparison.csproj | 2 +- .../index/ContinueWith-comparison/Program.cs | 4 + 5 files changed, 8 insertions(+), 151 deletions(-) delete mode 100644 docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj delete mode 100644 docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs diff --git a/docs/csharp/asynchronous-programming/index.md b/docs/csharp/asynchronous-programming/index.md index 2ae6ea45b10d4..be81a52d2bb17 100644 --- a/docs/csharp/asynchronous-programming/index.md +++ b/docs/csharp/asynchronous-programming/index.md @@ -265,7 +265,7 @@ The final code is asynchronous. It more accurately reflects how a person might c ## Async/await vs ContinueWith -The `async` and `await` keywords provide syntactic simplification over using directly. At the language level, `async`/`await` is essentially syntactic sugar that the compiler transforms into continuation-based code using `ContinueWith`. However, this transformation provides significant readability and maintainability benefits, especially when chaining multiple asynchronous operations. +The `async` and `await` keywords provide syntactic simplification over using directly. While `async`/`await` and `ContinueWith` have similar semantics for handling asynchronous operations, the compiler doesn't necessarily translate `await` expressions directly into `ContinueWith` method calls. Instead, the compiler generates optimized state machine code that provides the same logical behavior. This transformation provides significant readability and maintainability benefits, especially when chaining multiple asynchronous operations. Consider a scenario where you need to perform multiple sequential asynchronous operations. Here's how the same logic looks when implemented with `ContinueWith` compared to `async`/`await`: @@ -273,13 +273,13 @@ Consider a scenario where you need to perform multiple sequential asynchronous o With `ContinueWith`, each step in a sequence of asynchronous operations requires nested continuations: -:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="18-54"::: +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" id="ContinueWithExample"::: ### Using async/await The same sequence of operations using `async`/`await` reads much more naturally: -:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" range="57-73"::: +:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" id="AsyncAwaitExample"::: ### Why async/await is preferred diff --git a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj deleted file mode 100644 index d50c0e10aeba3..0000000000000 --- a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/AsyncBreakfast-ContinueWith.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - net8.0 - - - \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs b/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs deleted file mode 100644 index c9491701d2a38..0000000000000 --- a/docs/csharp/asynchronous-programming/snippets/index/AsyncBreakfast-ContinueWith/Program.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace AsyncBreakfastContinueWith -{ - // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose. - internal class Bacon { } - internal class Coffee { } - internal class Egg { } - internal class Juice { } - internal class Toast { } - - class Program - { - static Task Main(string[] args) - { - Coffee cup = PourCoffee(); - Console.WriteLine("coffee is ready"); - - var eggsTask = FryEggsAsync(2); - var baconTask = FryBaconAsync(3); - var toastTask = MakeToastWithButterAndJamUsingContinueWith(2); - - var breakfastTasks = new List { eggsTask, baconTask, toastTask }; - return ProcessTasksUsingContinueWith(breakfastTasks, eggsTask, baconTask, toastTask) - .ContinueWith(_ => - { - Juice oj = PourOJ(); - Console.WriteLine("oj is ready"); - Console.WriteLine("Breakfast is ready!"); - }); - } - - static Task ProcessTasksUsingContinueWith(List breakfastTasks, Task eggsTask, Task baconTask, Task toastTask) - { - return Task.WhenAny(breakfastTasks) - .ContinueWith(finishedTaskContainer => - { - var finishedTask = finishedTaskContainer.Result; - if (finishedTask == eggsTask) - { - Console.WriteLine("eggs are ready"); - } - else if (finishedTask == baconTask) - { - Console.WriteLine("bacon is ready"); - } - else if (finishedTask == toastTask) - { - Console.WriteLine("toast is ready"); - } - - breakfastTasks.Remove(finishedTask); - - // Continue processing remaining tasks if any - if (breakfastTasks.Count > 0) - { - return ProcessTasksUsingContinueWith(breakfastTasks, eggsTask, baconTask, toastTask); - } - else - { - return Task.CompletedTask; - } - }).Unwrap(); - } - - static Task MakeToastWithButterAndJamUsingContinueWith(int number) - { - return ToastBreadAsync(number) - .ContinueWith(toastTask => - { - var toast = toastTask.Result; - ApplyButter(toast); - ApplyJam(toast); - return toast; - }); - } - - private static Juice PourOJ() - { - Console.WriteLine("Pouring orange juice"); - return new Juice(); - } - - private static void ApplyJam(Toast toast) => - Console.WriteLine("Putting jam on the toast"); - - private static void ApplyButter(Toast toast) => - Console.WriteLine("Putting butter on the toast"); - - 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(3000); - Console.WriteLine("Remove toast from toaster"); - - return new Toast(); - } - - private static async Task FryBaconAsync(int slices) - { - Console.WriteLine($"putting {slices} slices of bacon in the pan"); - Console.WriteLine("cooking first side of bacon..."); - await Task.Delay(3000); - for (int slice = 0; slice < slices; slice++) - { - Console.WriteLine("flipping a slice of bacon"); - } - Console.WriteLine("cooking the second side of bacon..."); - await Task.Delay(3000); - Console.WriteLine("Put bacon on plate"); - - return new Bacon(); - } - - private static async Task FryEggsAsync(int howMany) - { - Console.WriteLine("Warming the egg pan..."); - await Task.Delay(3000); - Console.WriteLine($"cracking {howMany} eggs"); - Console.WriteLine("cooking the eggs ..."); - await Task.Delay(3000); - Console.WriteLine("Put eggs on plate"); - - return new Egg(); - } - - private static Coffee PourCoffee() - { - Console.WriteLine("Pouring coffee"); - return new Coffee(); - } - } -} \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj index d50c0e10aeba3..aee73149c0567 100644 --- a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj +++ b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/ContinueWith-comparison.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 \ No newline at end of file diff --git a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs index 2550e1e0385ec..2f12ffd4e5d1a 100644 --- a/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs +++ b/docs/csharp/asynchronous-programming/snippets/index/ContinueWith-comparison/Program.cs @@ -14,6 +14,7 @@ static async Task Main(string[] args) await MakeBreakfastWithAsyncAwait(); } + // // Using ContinueWith - demonstrates the complexity when chaining operations static Task MakeBreakfastWithContinueWith() { @@ -52,7 +53,9 @@ static Task MakeBreakfastWithContinueWith() Console.WriteLine("Breakfast completed with ContinueWith!"); }); } + // + // // Using async/await - much cleaner and easier to read static async Task MakeBreakfastWithAsyncAwait() { @@ -71,6 +74,7 @@ static async Task MakeBreakfastWithAsyncAwait() var finalToast = await ApplyJamAsync(butteredToast); Console.WriteLine("Breakfast completed with async/await!"); } + // static async Task StartCookingEggsAsync() {