CodeGym /Cursos /C# SELF /Excepciones en los clásicos

Excepciones en los clásicos Thread

C# SELF
Nivel 61 , Lección 3
Disponible

1. Introducción

Cuando trabajamos con la clase Task o con métodos asíncronos (async/await), podemos capturar excepciones de la forma habitual —con try-catch alrededor del await o usando ContinueWith. Las excepciones no "se pierden", sino que se devuelven al hilo llamante.

Pero si creamos un hilo con Thread, la cosa se complica. Cada hilo tiene su propio punto de entrada (ThreadStart) y su propio contexto de ejecución. Si dentro del hilo ocurre una excepción no manejada, no "volverá" al hilo principal — se lanzará solo en ese hilo.

  • En .NET Framework: una excepción no manejada en un hilo termina toda la aplicación.
  • En .NET (Core/5+): solo termina ese hilo, la aplicación continúa ejecutándose (lo que puede llevar a bugs silenciosos).

Conclusión: si no capturas las excepciones dentro del hilo, probablemente no las verás. Por eso el manejo correcto de errores en hilos es necesario.

Dato interesante: las excepciones que salen volando desde Thread son como un ninja escurridizo: desaparecen y luego te quedas rascándote la cabeza preguntándote por qué la lógica no funcionó.

2. ¿Cómo funcionan las excepciones dentro de un hilo?

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();

        // Esperamos a que termine el hilo para ver qué pasa
        thread.Join();

        Console.WriteLine("Main terminó normalmente");
    }

    static void DoWork()
    {
        Console.WriteLine("Trabajando en un hilo separado...");
        throw new Exception("¡Problema! Ocurrió un error en el hilo.");
    }
}

Dependiendo de la plataforma (el viejo .NET Framework o el moderno .NET Core/5/6/7/8/9), el comportamiento será distinto: o bien toda la aplicación caerá, o solo el hilo. Pero lo importante es que la excepción no llegará al hilo principal, y no podrás manejarla desde fuera.

¡Importante! Intentar envolver thread.Join() en un try-catch no te ayudará a atrapar la excepción de otro hilo — esa excepción "vive" y "muere" dentro de ese hilo.

3. ¿Cómo hay que atrapar excepciones en Thread?

Solo dentro del hilo — en la función que pasas al constructor de Thread. Todo lo que pueda lanzar errores, envuélvelo en try-catch.

static void DoWork()
{
    try
    {
        Console.WriteLine("Trabajando...");
        throw new Exception("¡Otra vez algo salió mal!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[Hilo] Capturada excepción: {ex.Message}");
        // Aquí puedes loguear, mandar al UI/servidor, etc.
    }
}

El manejo de excepciones en hilos es responsabilidad del código del hilo. No puedes contar con que el código llamante capture el error automáticamente.

4. ¿Cómo saber en el hilo principal que algo fue mal en otro hilo?

En aplicaciones reales es importante llevar la información del error al hilo principal.

  • Usa mecanismos seguros para hilos, por ejemplo ConcurrentQueue<Exception>, para pasar excepciones desde los hilos.
  • Lanza eventos/delegados desde el hilo de trabajo.
  • Prefiere Task, que entregará la excepción al await "out of the box".

Ejemplo: recogemos el error en un lugar especial

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($"En otro hilo ocurrió un error: {threadException.Message}");
        }
        else
        {
            Console.WriteLine("El hilo terminó sin errores.");
        }
    }

    static void DoWork()
    {
        try
        {
            throw new Exception("¡Problema en otro hilo!");
        }
        catch (Exception ex)
        {
            threadException = ex;
        }
    }
}

Nota: este enfoque sirve si esperas de forma sincrónica (Join()). Si el hilo "vive su propia vida" o hay muchos errores — usa ConcurrentQueue<Exception>, eventos u otros mecanismos de comunicación.

5. Comparación con Task: por qué el enfoque de manejo de errores es más sencillo

async Task FooAsync()
{
    throw new Exception("¡Error en la tarea!");
}

try
{
    await FooAsync();
}
catch (Exception ex)
{
    Console.WriteLine($"Capturada la excepción: {ex.Message}");
}

Aquí todo es transparente: el error "llega" hasta el lugar donde lo await-eas. Con el clásico Thread el error permanece dentro del hilo y no se transmite hacia arriba sin acciones especiales. Este es uno de los argumentos para usar Task y las abstracciones modernas.

6. Ejemplo práctico

En aplicaciones UI (WPF/WinForms) se usan hilos para no bloquear la interfaz. Excepciones sin manejar causan "pantalla gris" y comportamientos raros.

Mal (hilo sin manejo de errores)

Thread thread = new Thread(() =>
{
    // Pensando mucho
    Thread.Sleep(5000);
    throw new Exception("¡Todo se perdió!"); // nadie lo atrapará
});
thread.Start();

Bien (atrapamos el error y avisamos al usuario)

Thread thread = new Thread(() =>
{
    try
    {
        Thread.Sleep(5000);
        throw new Exception("Algo no está bien");
    }
    catch (Exception ex)
    {
        // Se puede mostrar MessageBox, loguear o reenviar al UI
        Console.WriteLine($"Error en el hilo: {ex.Message}");
    }
});
thread.Start();

7. Matices útiles

Hook global para excepciones no manejadas de hilos

AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
    Console.WriteLine($"Capturado globalmente el error: {((Exception)args.ExceptionObject).Message}");
};

Thread thread = new Thread(() =>
{
    throw new Exception("¡Exterminatus!");
});
thread.Start();

El manejador AppDomain.CurrentDomain.UnhandledException se dispara para excepciones no manejadas en hilos, pero no permite "resucitar" el hilo ni evitar la terminación del proceso en .NET Framework. En .NET (Core/5+) registra el error; la aplicación puede continuar si hay otros hilos activos.

Diferencias en el manejo de excepciones — Thread vs Task

Thread
Task
¿Dónde atrapar? Dentro del hilo En el código llamante (await, ContinueWith, etc.)
Consecuencias La excepción se pierde/mata el hilo (o toda la app en .NET Framework) La excepción llega al punto de espera (await)
Notificación hacia arriba Sólo explícita (variables, eventos, colas) A través de await, AggregateException en espera sincrónica
Logging Hay que hacerlo manualmente en el código del hilo Normalmente en try-catch alrededor del await
Contexto Independiente del hilo padre Task usa el synchronization context del código llamante (por ejemplo, el contexto UI en WPF)

8. Errores típicos al trabajar con excepciones en Thread

Error nº1: no atrapan excepciones dentro del hilo.
Como resultado puedes obtener la terminación silenciosa de parte de la aplicación, o a veces de todo el proceso, sin diagnóstico claro.

Error nº2: intentan "atrapar" la excepción del hilo en el hilo principal.
Esto no funciona: un try-catch alrededor de thread.Join() o thread.Start() no capturará la excepción lanzada dentro del hilo.

Error nº3: pierden la información del error.
Si el hilo falló y no pasaste la excepción explícitamente (variable, cola, evento), no sabrás ni la causa ni los detalles. Esto conduce a bugs "fantasma".

Error nº4: falta de logging.
Siempre loguea los errores en los hilos, aunque parezca que "no pasará nada".

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION