Skip to content

Commit e7ed445

Browse files
CopilotBillWagnergewarrenadegeo
authored
Add guidance for choosing ToList() vs ToArray() in async LINQ scenarios (#47729)
* Initial plan * Add explanation and example for ToList() vs ToArray() in async scenarios Co-authored-by: BillWagner <[email protected]> * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> * Update docs/csharp/asynchronous-programming/async-scenarios.md * Add complete snippet markers to Program.cs and update markdown reference Co-authored-by: adegeo <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]> Co-authored-by: Bill Wagner <[email protected]> Co-authored-by: Genevieve Warren <[email protected]> Co-authored-by: adegeo <[email protected]>
1 parent 83702f4 commit e7ed445

File tree

2 files changed

+29
-15
lines changed

2 files changed

+29
-15
lines changed

docs/csharp/asynchronous-programming/async-scenarios.md

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,25 +132,18 @@ You can write this code more succinctly by using LINQ:
132132

133133
Although you write less code by using LINQ, exercise caution when mixing LINQ with asynchronous code. LINQ uses deferred (or lazy) execution, which means that without immediate evaluation, async calls don't happen until the sequence is enumerated.
134134

135-
The previous example is correct and safe, because it uses the <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> method to immediately evaluate the LINQ query and store the tasks in an array. This approach ensures the `id => GetUserAsync(id)` calls execute immediately and all tasks start concurrently, just like the `foreach` loop approach.
135+
The previous example is correct and safe, because it uses the <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> method to immediately evaluate the LINQ query and store the tasks in an array. This approach ensures the `id => GetUserAsync(id)` calls execute immediately and all tasks start concurrently, just like the `foreach` loop approach. Always use <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> or <xref:System.Linq.Enumerable.ToList%2A?displayProperty=nameWithType> when creating tasks with LINQ to ensure immediate execution and concurrent task execution. Here's an example that demonstrates using `ToList()` with `Task.WhenAny` to process tasks as they complete:
136136

137-
**Problematic approach** (without immediate evaluation):
137+
:::code language="csharp" source="snippets/async-scenarios/Program.cs" ID="ProcessTasksAsTheyComplete":::
138138

139-
```csharp
140-
// DON'T do this - tasks won't start until enumerated.
141-
var getUserTasks = userIds.Select(id => GetUserAsync(id)); // No .ToArray()!
142-
return await Task.WhenAll(getUserTasks); // Tasks start here.
143-
```
139+
In this example, `ToList()` creates a list that supports the `Remove()` operation, allowing you to dynamically remove completed tasks. This pattern is particularly useful when you want to handle results as soon as they're available, rather than waiting for all tasks to complete.
144140

145-
**Recommended approach**:
141+
Although you write less code by using LINQ, exercise caution when mixing LINQ with asynchronous code. LINQ uses deferred (or lazy) execution. Asynchronous calls don't happen immediately as they do in a `foreach` loop, unless you force the generated sequence to iterate with a call to the `.ToList()` or `.ToArray()` method.
146142

147-
```csharp
148-
// DO this - tasks start immediately.
149-
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
150-
return await Task.WhenAll(getUserTasks);
151-
```
143+
You can choose between <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> and <xref:System.Linq.Enumerable.ToList%2A?displayProperty=nameWithType> based on your scenario:
152144

153-
Always use <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> or <xref:System.Linq.Enumerable.ToList%2A?displayProperty=nameWithType> when creating tasks with LINQ to ensure immediate execution and concurrent task execution.
145+
- Use `ToArray()` when you plan to process all tasks together, such as with `Task.WhenAll`. Arrays are efficient for scenarios where the collection size is fixed.
146+
- Use `ToList()` when you need to dynamically manage tasks, such as with `Task.WhenAny` where you might remove completed tasks from the collection as they finish.
154147

155148
## Review considerations for asynchronous programming
156149

@@ -280,7 +273,7 @@ For more detailed guidance on the challenges and considerations of synchronous w
280273

281274
The following code represents the complete example, which is available in the *Program.cs* example file.
282275

283-
:::code language="csharp" source="snippets/async-scenarios/Program.cs":::
276+
:::code language="csharp" source="snippets/async-scenarios/Program.cs" ID="complete":::
284277

285278
## Related links
286279

docs/csharp/asynchronous-programming/snippets/async-scenarios/Program.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// <complete>
12
using System.Text.RegularExpressions;
23
using System.Windows;
34
using Microsoft.AspNetCore.Mvc;
@@ -145,6 +146,22 @@ private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
145146
}
146147
// </GetUsersForDatasetByLINQ>
147148

149+
// <ProcessTasksAsTheyComplete>
150+
private static async Task ProcessTasksAsTheyCompleteAsync(IEnumerable<int> userIds)
151+
{
152+
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToList();
153+
154+
while (getUserTasks.Count > 0)
155+
{
156+
Task<User> completedTask = await Task.WhenAny(getUserTasks);
157+
getUserTasks.Remove(completedTask);
158+
159+
User user = await completedTask;
160+
Console.WriteLine($"Processed user {user.id}");
161+
}
162+
}
163+
// </ProcessTasksAsTheyComplete>
164+
148165
// <ExtractDataFromNetwork>
149166
[HttpGet, Route("DotNetCount")]
150167
static public async Task<int> GetDotNetCountAsync(string URL)
@@ -178,6 +195,9 @@ static async Task Main()
178195
Console.WriteLine($"{user.id}: isEnabled={user.isEnabled}");
179196
}
180197

198+
Console.WriteLine("Processing tasks as they complete...");
199+
await ProcessTasksAsTheyCompleteAsync(ids);
200+
181201
Console.WriteLine("Application ending.");
182202
}
183203
}
@@ -219,4 +239,5 @@ static async Task Main()
219239
// 9: isEnabled= False
220240
// 0: isEnabled= False
221241
// Application ending.
242+
// </complete>
222243

0 commit comments

Comments
 (0)