DotNet WhenAll LoadAsync

Prompt

Como evitar task.whenall com multiplos loadasync, utilizado com DbContext?

Google Modo IA

Para o Entity Framework Core, o uso de Task.WhenAll com o mesmo DbContext resultará no erro: “A second operation started on this context before a previous operation completed”. Isso ocorre porque o DbContext não é thread-safe e não suporta operações paralelas simultâneas na mesma instância.
Para resolver isso e evitar o Task.WhenAll problemático, você tem três caminhos principais:
1. Utilize IDbContextFactory (Recomendado para Paralelismo)
Se você precisa carregar dados em paralelo para ganhar performance, a solução oficial é injetar o IDbContextFactory. Isso permite criar uma instância isolada do contexto para cada tarefa.
csharp
// No construtor: private readonly IDbContextFactory<MyDbContext> _contextFactory;

public async Task LoadDataInParallel()
{
    var task1 = Task.Run(async () => {
        using var context = _contextFactory.CreateDbContext();
        return await context.Users.ToListAsync();
    });

    var task2 = Task.Run(async () => {
        using var context = _contextFactory.CreateDbContext();
        return await context.Orders.ToListAsync();
    });

    await Task.WhenAll(task1, task2); // Agora é seguro pois cada Task tem seu próprio Context
}
Use o código com cuidado.

2. Execução Sequencial (Simples e Segura)
Se a performance de carregar tudo ao mesmo tempo não for crítica, a forma mais segura de evitar o erro é simplesmente aguardar cada chamada individualmente. Isso garante que apenas uma operação utilize a conexão por vez.
csharp
var users = await _context.Users.ToListAsync();
var orders = await _context.Orders.ToListAsync(); // Só inicia após o término da anterior
Use o código com cuidado.

3. Projeção Única (Query Splitting ou Joins)
Em vez de múltiplos LoadAsync, tente consolidar as buscas em uma única consulta ao banco ou utilize o AsSplitQuery() se o problema for o carregamento de muitas coleções relacionadas (Avoid Cartesian Explosion).
Resumo de quando usar cada um:
  • IDbContextFactory: Use quando o paralelismo é indispensável (ex: Dashboards complexos ou Blazor Server).
  • Await sequencial: Use em fluxos simples de CRUD onde a latência do banco é baixa.
  • Split Queries: Use se os múltiplos “loads” forem para carregar propriedades de navegação de uma mesma entidade.
Você está enfrentando esse problema em uma aplicação Web API comum ou em um cenário de Background Service/Blazor?

Prompt

Como evitar task.whenall com multiplos loadasync?

Google Modo IA

Para evitar o uso de Task.WhenAll ao lidar com múltiplos LoadAsync, você pode adotar diferentes padrões dependendo do seu objetivo (performance, controle de concorrência ou ordem de execução):
1. Processamento Conforme Conclusão (Task.WhenAny)
Se você não quer esperar todos terminarem para começar a processar os resultados, use o padrão de remover tarefas de uma lista conforme elas finalizam. Isso melhora a responsividade.
csharp
var tasks = new List<Task<Data>> { LoadAsync(1), LoadAsync(2), LoadAsync(3) };
while (tasks.Count > 0)
{
    var finishedTask = await Task.WhenAny(tasks);
    tasks.Remove(finishedTask);
    var data = await finishedTask; // Processa assim que cada um termina
}
Use o código com cuidado.

2. Controle de Concorrência (SemaphoreSlim)
Para evitar sobrecarregar o sistema com muitos LoadAsync simultâneos (o que o WhenAll faz por padrão), utilize um SemaphoreSlim para limitar quantas tarefas rodam ao mesmo tempo.
csharp
var semaphore = new SemaphoreSlim(3); // Limite de 3 simultâneos
var tasks = items.Select(async item => {
    await semaphore.WaitAsync();
    try { return await LoadAsync(item); }
    finally { semaphore.Release(); }
});
await Task.WhenAll(tasks); // Ainda usa WhenAll, mas com execução controlada
Use o código com cuidado.

3. Iteração Assíncrona Moderna (Parallel.ForEachAsync)
No .NET 6+, a melhor alternativa ao WhenAll para coleções é o Parallel.ForEachAsync. Ele gerencia automaticamente o grau de paralelismo e é mais eficiente para processar grandes volumes de dados.
csharp
await Parallel.ForEachAsync(items, async (item, token) => {
    await LoadAsync(item); // Execução paralela controlada nativamente
});
Use o código com cuidado.

4. Execução Sequencial (Simples await)
Se os carregamentos dependem um do outro ou se a ordem é crítica, abandone o paralelismo e use o await individual. Isso evita a complexidade de gerenciar múltiplas tarefas.
csharp
var res1 = await LoadAsync(1);
var res2 = await LoadAsync(2); // Só inicia após o primeiro terminar
Use o código com cuidado.

Qual é o cenário específico dos seus LoadAsync (ex: muitas chamadas de API, leitura de arquivos ou carregamento de UI)?