Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions docs/csharp/asynchronous-programming/async-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,25 +132,18 @@ You can write this code more succinctly by using LINQ:

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.

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.
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:

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

```csharp
// DON'T do this - tasks won't start until enumerated.
var getUserTasks = userIds.Select(id => GetUserAsync(id)); // No .ToArray()!
return await Task.WhenAll(getUserTasks); // Tasks start here.
```
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.

**Recommended approach**:
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.

```csharp
// DO this - tasks start immediately.
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
```
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:

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

## Review considerations for asynchronous programming

Expand Down Expand Up @@ -212,7 +205,7 @@ A recommended goal is to achieve complete or near-complete [Referential Transpar

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

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

## Related links

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// <complete>
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -145,6 +146,22 @@ private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
}
// </GetUsersForDatasetByLINQ>

// <ProcessTasksAsTheyComplete>
private static async Task ProcessTasksAsTheyCompleteAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToList();

while (getUserTasks.Count > 0)
{
Task<User> completedTask = await Task.WhenAny(getUserTasks);
getUserTasks.Remove(completedTask);

User user = await completedTask;
Console.WriteLine($"Processed user {user.id}");
}
}
// </ProcessTasksAsTheyComplete>

// <ExtractDataFromNetwork>
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCountAsync(string URL)
Expand Down Expand Up @@ -178,6 +195,9 @@ static async Task Main()
Console.WriteLine($"{user.id}: isEnabled={user.isEnabled}");
}

Console.WriteLine("Processing tasks as they complete...");
await ProcessTasksAsTheyCompleteAsync(ids);

Console.WriteLine("Application ending.");
}
}
Expand Down Expand Up @@ -219,4 +239,5 @@ static async Task Main()
// 9: isEnabled= False
// 0: isEnabled= False
// Application ending.
// </complete>