Skip to content

Commit 1fa7d1b

Browse files
CopilotBillWagner
andauthored
Compare async/await to ContinueWith (#47075)
* Initial plan * Add ContinueWith vs async/await comparison section Co-authored-by: BillWagner <[email protected]> * Fix code snippet ranges for ContinueWith comparison Co-authored-by: BillWagner <[email protected]> * Address review comments: update .NET target, remove unused files, fix misleading text, use tag-based snippets Co-authored-by: BillWagner <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]>
1 parent ee36d6a commit 1fa7d1b

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

docs/csharp/asynchronous-programming/index.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,36 @@ The code completes the asynchronous breakfast tasks in about 15 minutes. The tot
263263

264264
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.
265265

266+
## Async/await vs ContinueWith
267+
268+
The `async` and `await` keywords provide syntactic simplification over using <xref:System.Threading.Tasks.Task.ContinueWith%2A?displayProperty=nameWithType> 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.
269+
270+
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`:
271+
272+
### Using ContinueWith
273+
274+
With `ContinueWith`, each step in a sequence of asynchronous operations requires nested continuations:
275+
276+
:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" id="ContinueWithExample":::
277+
278+
### Using async/await
279+
280+
The same sequence of operations using `async`/`await` reads much more naturally:
281+
282+
:::code language="csharp" source="snippets/index/ContinueWith-comparison/Program.cs" id="AsyncAwaitExample":::
283+
284+
### Why async/await is preferred
285+
286+
The `async`/`await` approach offers several advantages:
287+
288+
- **Readability**: The code reads like synchronous code, making it easier to understand the flow of operations.
289+
- **Maintainability**: Adding or removing steps in the sequence requires minimal code changes.
290+
- **Error handling**: Exception handling with `try`/`catch` blocks works naturally, whereas `ContinueWith` requires careful handling of faulted tasks.
291+
- **Debugging**: The call stack and debugger experience is much better with `async`/`await`.
292+
- **Performance**: The compiler optimizations for `async`/`await` are more sophisticated than manual `ContinueWith` chains.
293+
294+
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.
295+
266296
## Next step
267297

268298
> [!div class="nextstepaction"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace ContinueWithComparison
5+
{
6+
class Program
7+
{
8+
static async Task Main(string[] args)
9+
{
10+
Console.WriteLine("=== ContinueWith approach ===");
11+
await MakeBreakfastWithContinueWith();
12+
13+
Console.WriteLine("\n=== async/await approach ===");
14+
await MakeBreakfastWithAsyncAwait();
15+
}
16+
17+
// <ContinueWithExample>
18+
// Using ContinueWith - demonstrates the complexity when chaining operations
19+
static Task MakeBreakfastWithContinueWith()
20+
{
21+
return StartCookingEggsAsync()
22+
.ContinueWith(eggsTask =>
23+
{
24+
var eggs = eggsTask.Result;
25+
Console.WriteLine("Eggs ready, starting bacon...");
26+
return StartCookingBaconAsync();
27+
})
28+
.Unwrap()
29+
.ContinueWith(baconTask =>
30+
{
31+
var bacon = baconTask.Result;
32+
Console.WriteLine("Bacon ready, starting toast...");
33+
return StartToastingBreadAsync();
34+
})
35+
.Unwrap()
36+
.ContinueWith(toastTask =>
37+
{
38+
var toast = toastTask.Result;
39+
Console.WriteLine("Toast ready, applying butter...");
40+
return ApplyButterAsync(toast);
41+
})
42+
.Unwrap()
43+
.ContinueWith(butteredToastTask =>
44+
{
45+
var butteredToast = butteredToastTask.Result;
46+
Console.WriteLine("Butter applied, applying jam...");
47+
return ApplyJamAsync(butteredToast);
48+
})
49+
.Unwrap()
50+
.ContinueWith(finalToastTask =>
51+
{
52+
var finalToast = finalToastTask.Result;
53+
Console.WriteLine("Breakfast completed with ContinueWith!");
54+
});
55+
}
56+
// </ContinueWithExample>
57+
58+
// <AsyncAwaitExample>
59+
// Using async/await - much cleaner and easier to read
60+
static async Task MakeBreakfastWithAsyncAwait()
61+
{
62+
var eggs = await StartCookingEggsAsync();
63+
Console.WriteLine("Eggs ready, starting bacon...");
64+
65+
var bacon = await StartCookingBaconAsync();
66+
Console.WriteLine("Bacon ready, starting toast...");
67+
68+
var toast = await StartToastingBreadAsync();
69+
Console.WriteLine("Toast ready, applying butter...");
70+
71+
var butteredToast = await ApplyButterAsync(toast);
72+
Console.WriteLine("Butter applied, applying jam...");
73+
74+
var finalToast = await ApplyJamAsync(butteredToast);
75+
Console.WriteLine("Breakfast completed with async/await!");
76+
}
77+
// </AsyncAwaitExample>
78+
79+
static async Task<object> StartCookingEggsAsync()
80+
{
81+
Console.WriteLine("Starting to cook eggs...");
82+
await Task.Delay(1000);
83+
return new { Item = "Eggs" };
84+
}
85+
86+
static async Task<object> StartCookingBaconAsync()
87+
{
88+
Console.WriteLine("Starting to cook bacon...");
89+
await Task.Delay(1000);
90+
return new { Item = "Bacon" };
91+
}
92+
93+
static async Task<object> StartToastingBreadAsync()
94+
{
95+
Console.WriteLine("Starting to toast bread...");
96+
await Task.Delay(1000);
97+
return new { Item = "Toast" };
98+
}
99+
100+
static async Task<object> ApplyButterAsync(object toast)
101+
{
102+
Console.WriteLine("Applying butter...");
103+
await Task.Delay(500);
104+
return new { Item = "Buttered Toast" };
105+
}
106+
107+
static async Task<object> ApplyJamAsync(object butteredToast)
108+
{
109+
Console.WriteLine("Applying jam...");
110+
await Task.Delay(500);
111+
return new { Item = "Completed Toast" };
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)