Skip to content

Add guidance for choosing ToList() vs ToArray() in async LINQ scenarios #47729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 14 additions & 1 deletion docs/csharp/asynchronous-programming/async-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,20 @@

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

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. This example uses the <xref:System.Linq.Enumerable.ToArray%2A?displayProperty=nameWithType> method to perform the query eagerly and store the results in an array. This approach forces the `id => GetUserAsync(id)` statement to run and initiate the task.
Here's an example that demonstrates using `ToList()` with `Task.WhenAny` to process tasks as they complete:

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

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.

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.

Check failure on line 139 in docs/csharp/asynchronous-programming/async-scenarios.md

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces

docs/csharp/asynchronous-programming/async-scenarios.md:139:324 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.38.0/doc/md009.md

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:

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

This example uses the `ToArray()` method to perform the query eagerly and store the results in an array. This approach forces the `id => GetUserAsync(id)` statement to run and initiate the task.

## Review considerations for asynchronous programming

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,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 +194,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
Loading