1. Introdução
Quando trabalhamos com a classe Task ou métodos assíncronos (async/await), a gente pode capturar exceções do jeito que tá acostumado — com try-catch em volta do await ou usando ContinueWith. As exceções não "se perdem", elas retornam pro thread chamador.
Mas se a gente cria um thread via Thread, a coisa fica mais complicada. Cada thread tem seu ponto de entrada (ThreadStart) e seu contexto de execução. Se dentro do thread acontecer uma exceção não tratada, ela não "volta" pro thread principal — ela é lançada apenas naquele thread.
- No .NET Framework: uma exceção não tratada em um thread termina toda a aplicação.
- No .NET (Core/5+): somente esse thread vai terminar, a aplicação continua rodando (o que pode levar a bugs escondidos).
Conclusão: se você não capturar exceções dentro do thread, provavelmente vai simplesmente não vê-las. Por isso um tratamento correto de erros em threads é essencial.
Fato interessante: exceções que escapam do Thread são como ninjas invisíveis: somem, e você fica quebrando a cabeça porque a lógica não funcionou.
2. Como as exceções funcionam dentro do thread?
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(DoWork);
thread.Start();
// Vamos esperar o término do thread pra ver o que acontece
thread.Join();
Console.WriteLine("Main finalizou normalmente");
}
static void DoWork()
{
Console.WriteLine("Trabalhando em um thread separado...");
throw new Exception("Problema! Ocorreu um erro no thread.");
}
}
Dependendo da plataforma (o velho .NET Framework ou o moderno .NET Core/5/6/7/8/9), o comportamento será diferente: ou a aplicação inteira cai, ou só o thread. Mas o principal — a exceção não vai chegar no thread principal, e você não vai conseguir tratá-la de fora.
Importante! Tentar envolver thread.Join() em um try-catch não vai ajudar a capturar a exceção de outro thread — ela "vive" e "morre" dentro dele.
3. Onde capturar exceções em Thread?
Somente dentro do próprio thread — na função que você passa pro construtor do Thread. Tudo que pode lançar erro, envolva em try-catch.
static void DoWork()
{
try
{
Console.WriteLine("Trabalhando...");
throw new Exception("De novo algo deu errado!");
}
catch (Exception ex)
{
Console.WriteLine($"[Thread] Pegamos a exceção: {ex.Message}");
// Aqui dá pra logar, mandar pro UI/servidor etc.
}
}
O tratamento de exceções em threads é responsabilidade do código do thread. Não dá pra contar que o código chamador vai capturar o erro automaticamente.
4. Como saber no thread principal que algo deu errado em outro thread?
Em aplicações reais é importante levar a informação do erro pro thread principal.
- Use mecanismos thread-safe, por exemplo ConcurrentQueue<Exception>, pra passar exceções dos threads.
- Levante eventos/delegates a partir do thread de trabalho.
- Prefira Task, que entrega a exceção até o await "out of the box".
Exemplo: juntando o erro em um lugar específico
using System;
using System.Threading;
class Program
{
static Exception? threadException = null;
static void Main()
{
Thread thread = new Thread(DoWork);
thread.Start();
thread.Join();
if (threadException != null)
{
Console.WriteLine($"Ocorreu um erro em outro thread: {threadException.Message}");
}
else
{
Console.WriteLine("O thread terminou sem erros.");
}
}
static void DoWork()
{
try
{
throw new Exception("Problema em outro thread!");
}
catch (Exception ex)
{
threadException = ex;
}
}
}
Observação: essa abordagem funciona quando você espera sincronicamente (Join()). Se o thread "vive por conta própria" ou há muitos erros — use ConcurrentQueue<Exception>, eventos ou outros mecanismos de comunicação.
5. Comparação com Task: por que é mais simples lidar com erros
async Task FooAsync()
{
throw new Exception("Erro na task!");
}
try
{
await FooAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Pegamos o erro: {ex.Message}");
}
Aqui é tudo transparente: o erro "chega" até onde você faz o await. Com o Thread clássico o erro fica dentro do thread e não é passado pra cima sem ações explícitas. Esse é um dos motivos pra preferir Task e abstrações modernas.
6. Exemplo prático
Em apps com UI (WPF/WinForms) threads são usados pra não travar a interface. Exceções sem tratamento levam à "tela cinza" e travamentos estranhos.
Ruim (thread sem tratamento de erro)
Thread thread = new Thread(() =>
{
// Pensando por um bom tempo
Thread.Sleep(5000);
throw new Exception("Tudo perdido!"); // ninguém vai pegar
});
thread.Start();
Bom (capturamos o erro e avisamos o usuário)
Thread thread = new Thread(() =>
{
try
{
Thread.Sleep(5000);
throw new Exception("Algo deu errado");
}
catch (Exception ex)
{
// Dá pra mostrar um MessageBox, logar ou enviar pro UI
Console.WriteLine($"Erro no thread: {ex.Message}");
}
});
thread.Start();
7. Dicas úteis
Hook global pra exceções não tratadas em threads
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Console.WriteLine($"Capturado globalmente: {((Exception)args.ExceptionObject).Message}");
};
Thread thread = new Thread(() =>
{
throw new Exception("Exterminatus!");
});
thread.Start();
O handler AppDomain.CurrentDomain.UnhandledException dispara para exceções não tratadas em threads, mas não permite "ressuscitar" o thread ou evitar o término do processo no .NET Framework. No .NET (Core/5+) ele loga o erro; a aplicação pode continuar rodando se outros threads estiverem ativos.
Diferenças no tratamento de exceções — Thread vs Task
|
|
|
|---|---|---|
| Onde capturar | Dentro do thread | No código chamador (await, ContinueWith, etc.) |
| Consequências | A exceção se perde/mata o thread (ou toda a aplicação no .NET Framework) | A exceção chega até o ponto de espera (await) |
| Notificação pra cima | Apenas explicitamente (variáveis, eventos, filas) | Através do await, AggregateException em espera síncrona |
| Logging | Tem que fazer manualmente dentro do código do thread | Geralmente em try-catch ao redor do await |
| Contexto | Independente do thread pai | Task usa o contexto de sincronização do chamador (por exemplo, contexto de UI no WPF) |
8. Erros típicos ao trabalhar com exceções em Thread
Erro nº1: não capturar exceções dentro do thread.
No fim você pode ter o término silencioso de parte da aplicação, ou até do processo inteiro, sem diagnóstico claro.
Erro nº2: tentar "pegar" a exceção do thread no thread principal.
Isso não funciona: um try-catch em volta de thread.Join() ou thread.Start() não vai capturar uma exceção lançada dentro do thread.
Erro nº3: perder informação do erro.
Se o thread caiu e você não passou a exceção explicitamente (variável, fila, evento), você não vai saber a causa nem os detalhes. Isso leva a bugs "fantasmas".
Erro nº4: falta de logging.
Sempre logue erros em threads, mesmo que pareça que "não vai ser nada demais".
GO TO FULL VERSION