Sitemap

Programação Assíncrona em .NET: Evitando Armadilhas Comuns

5 min readSep 17, 2025
Programação Assíncrona em .NET: Evitando Armadilhas Comuns

Há tempos venho percebendo que durante avaliação para contratação de desenvolvedores, muitos entrevistados não conhecem os conceitos básicos ou tropeçam ao explicá-los, prejudicando a sua avaliação e posterior contratação, pois são conceitos básicos, importantes e que não podem ser negligenciados.

Decidi compartilhar neste artigo algumas insights cruciais sobre programação assíncrona.

Vamos focar em três temas principais:

  • a manipulação de código assíncrono usando async/await e Tasks
  • a confusão comum entre paralelismo e assincronia
  • os perigos de bloquear a thread principal com métodos como Task.Result ou Wait().

Vou incluir exemplos de código práticos que você pode testar diretamente no seu ambiente .NET.

Este artigo é baseado na minha prática diária e visa ajudar você a escrever código mais eficiente e livre de deadlocks.

Mas o que é o conceito?

Podemos ver na imagem abaixo, que num processo “síncrono” (synchronous) o fluxo “A” para, esperando que o fluxo “B” finalize para que ele prossiga.

Em termos técnicos, em um processo síncrono, a thread que executa a operação fica bloqueada até que a tarefa (como uma chamada a uma API ou leitura de arquivo) esteja concluída. Isso consome recursos da thread e pode causar lentidão, especialmente em operações de I/O (entrada/saída) demoradas.

Já no “assíncrono” (asynchronous) vemos que o fluxo do processo A, é executado e dispara o processo B, porém ele não bloqueia a thread principal, que segue em execução, até que ele necessite da resposta do processo B para alguma coisa.

A thread principal é liberada para realizar outras tarefas enquanto “B” (ex.: uma chamada HTTP) é processada no background, geralmente pelo thread pool ou pelo sistema operacional. Quando o resultado de “B” é necessário, o await retoma a execução no ponto correto!

ilustração sobre diferenças entre síncrono e assíncrono

Manipulação de Código Assíncrono com async/await e Tasks

A programação assíncrona em .NET é uma ferramenta poderosa para lidar com operações de I/O-bound, como chamadas a APIs, leitura de arquivos ou consultas a bancos de dados, sem bloquear threads sem necessidade.

Os pilares aqui são as palavras-chave async e await, combinadas com a classe Task (ou Task<T> para resultados tipados).

Conceitos Básicos

  • Task: Representa uma operação assíncrona que pode ser aguardada. Pode ser criada manualmente com Task.Run() para CPU-bound ou retornada por métodos assíncronos.
  • async/await: Permite escrever código assíncrono de forma síncrona, facilitando a leitura. O await suspende a execução até a tarefa completar, liberando a thread para outros trabalhos.

Exemplo Prático

Vamos criar um método simples que faz uma chamada HTTP assíncrona para buscar dados de uma API. Use isso em um console app .NET para testar

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Iniciando chamada assíncrona...");
string resultado = await BuscarDadosAsync();
Console.WriteLine($"Resultado: {resultado}");
}

static async Task<string> BuscarDadosAsync()
{
using var client = new HttpClient();
// Simulando uma chamada a uma API pública
string url = "https://jsonplaceholder.typicode.com/todos/1";
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}

Teste isso: Rode o programa e observe que a thread principal não fica bloqueada durante a requisição. O await permite que o código continue fluindo naturalmente.

Dicas Avançadas

  • Use ConfigureAwait(false) em métodos de biblioteca para evitar capturar o contexto de sincronização (útil em apps sem UI).
  • Para múltiplas tarefas, use Task.WhenAll() para aguardar todas de uma vez, executando as chamadas sem o “await”.

Exemplo de uso Task.WhenAll :

using System;
using System.Threading.Tasks;

class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Iniciando tarefas...");

// Cria duas tarefas assíncronas
Task tarefa1 = ProcessarTarefa1Async();
Task tarefa2 = ProcessarTarefa2Async();

// Usa Task.WaitAll para aguardar todas as tarefas (síncrono)
Task.WaitAll(tarefa1, tarefa2);

Console.WriteLine("Todas as tarefas concluídas!");
}

static async Task ProcessarTarefa1Async()
{
Console.WriteLine("Tarefa 1: Iniciando processamento...");
await Task.Delay(2000); // Simula 2 segundos de trabalho
Console.WriteLine("Tarefa 1: Concluída!");
}

static async Task ProcessarTarefa2Async()
{
Console.WriteLine("Tarefa 2: Iniciando processamento...");
await Task.Delay(3000); // Simula 3 segundos de trabalho
Console.WriteLine("Tarefa 2: Concluída!");
}
}

Confundindo Paralelismo com Assincronia

Uma armadilha comum é misturar paralelismo (execução simultânea em múltiplas threads para CPU-bound) com assincronia (liberação de threads para I/O-bound). Paralelismo acelera computações intensivas usando múltiplos núcleos, enquanto assincronia otimiza esperas por recursos externos.

Diferenças Chave

  • Assincronia: Não cria novas threads necessariamente; usa o thread pool para callbacks. Ideal para web requests ou DB queries.
  • Paralelismo: Usa explicitamente múltiplas threads via Parallel.ForEach ou Task.Run em loops. Pode sobrecarregar o sistema se mal usado.

Confundir os dois leva a ineficiências, como rodar tarefas I/O em threads dedicadas desnecessariamente, consumindo recursos.

Exemplo incorreto, confundindo conceitos:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
static async Task Main(string[] args)
{
var urls = new List<string> { "https://jsonplaceholder.typicode.com/todos/1", "https://jsonplaceholder.typicode.com/todos/2" };

// Errado: Usando Task.Run para paralelismo em I/O-bound
var tasks = urls.Select(url => Task.Run(async () => await BuscarDadosAsync(url)));
await Task.WhenAll(tasks);

Console.WriteLine("Concluído.");
}

static async Task<string> BuscarDadosAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
}

Isso cria threads extras desnecessariamente, pois as chamadas HTTP são I/O-bound e já são assíncronas.

Correção (Pura Assincronia):

// Mesma lista de URLs
var tasks = urls.Select(url => BuscarDadosAsync(url));
await Task.WhenAll(tasks);

Bloqueando a Thread Principal com Task.Result ou Wait()

Métodos como Task.Result ou Task.Wait() forçam a espera síncrona por uma tarefa assíncrona, bloqueando a thread chamadora. Isso é perigoso em contextos como ASP.NET ou apps com UI, podendo causar deadlocks ou perda de responsividade.

Por Que Evitar?

  • Em um SynchronizationContext (como em WinForms ou ASP.NET), o await captura o contexto para continuar na mesma thread. Bloquear com Result pode impedir a continuação, levando a deadlock.
  • Alternativa: Sempre use await para aguardar tarefas.

Exemplo de Problema e Solução

Exemplo com Deadlock Potencial:

using System;
using System.Threading.Tasks;

class Program
{
static void Main(string[] args)
{
// Bloqueando a thread principal
var task = TarefaAssincrona();
string resultado = task.Result; // Pode causar deadlock!
Console.WriteLine(resultado);
}

static async Task<string> TarefaAssincrona()
{
await Task.Delay(1000); // Simula operação assíncrona
return "Concluído";
}
}

Em um app com contexto (ex: WPF), isso pode travar.

Solução Correta:

static async Task Main(string[] args)
{
var task = TarefaAssincrona();
string resultado = await task;
Console.WriteLine(resultado);
}

Teste isso: Rode em um console app vs. um app WPF para ver o deadlock no primeiro caso.

Conclusão

Dominar a programação assíncrona em .NET exige entender não só as ferramentas como async/await e Tasks, mas também evitar confusões com paralelismo e bloqueios desnecessários. Esses erros podem transformar uma aplicação performática em um pesadelo de threads travadas. Experimente os códigos acima no seu Visual Studio ou dotnet CLI e veja a diferença na prática!

Gostou do artigo? clique no ícone👏e me siga para ver as próximas publicações !! Quer ver mais conteúdos, acesse minhas redes através do Linktree: https://linktree.com/nizzola

Referências

Se tiver mais dúvidas, este artigo do lendário Macoratti vai ajudar a entender um pouco mais !

https://www.macoratti.net/20/11/c_whenall1.htm

--

--

Marcio Nizzola
Marcio Nizzola

Written by Marcio Nizzola

Microsoft MVP | Master Software Architect na CI&T | Prof. da Etec Itu | Membro Fundador da Comunidade Itu Developers.

No responses yet