CodeGym /Cursos /C# SELF /Exceções em Thread ...

Exceções em Thread clássicos

C# SELF
Nível 61, Lição 3
Disponível

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

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

2
Tarefa
C# SELF, nível 61, lição 3
Bloqueado
Manipulador global de exceções não tratadas
Manipulador global de exceções não tratadas
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION