CodeGym /Cursos /C# SELF /Ciclo de vida de un hilo y su gestión

Ciclo de vida de un hilo y su gestión

C# SELF
Nivel 55 , Lección 2
Disponible

1. Introducción

Imagínate que un hilo es un empleado incansable a quien le asignas trabajo. El empleado puede estar durmiendo (aún no empezó a trabajar), currando a tope (se está ejecutando tu método), esperando a que le des una nueva tarea (idle), o terminar su jornada (finalizar).

En C# (y en .NET en general) el ciclo de vida de un hilo consta de varios estados:

  • Unstarted — el hilo está creado, pero todavía no arrancó.
  • Running — el hilo se está ejecutando.
  • WaitSleepJoin — el hilo está temporalmente inactivo (por ejemplo, esperando una señal o "durmiendo").
  • Stopped — el hilo completó la tarea y terminó.

Puedes visualizar este ciclo con este esquema:

stateDiagram-v2
    [*] --> Unstarted
    Unstarted --> Running: Start()
    Running --> WaitSleepJoin: Wait/Sleep/Join
    WaitSleepJoin --> Running: Señal recibida/Tiempo agotado
    Running --> Stopped: Método finalizado
    WaitSleepJoin --> Stopped: Método finalizado
    Stopped --> [*]

Todo empieza con la creación del objeto Thread, pero hasta que no llames a Start() el hilo "duerme" en el estado Unstarted. Tras llamar a Start() — empieza la fiesta, el hilo pasa a Running. Si el hilo dentro del código ejecuta Thread.Sleep o espera algo (por ejemplo, Monitor.Wait), entrará en un estado especial de espera. En cuanto el método pasado al hilo finaliza, el hilo muere, deja de existir y no "resucita". Es un viaje de ida.

2. Práctica: ciclo de vida de un hilo simple

Veamos un ejemplo clásico:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // Creamos el hilo — por ahora solo planificamos el trabajo
        Thread worker = new Thread(DoWork);

        Console.WriteLine($"Estado del hilo después de crear: {worker.ThreadState}");

        // Arrancamos el hilo
        worker.Start();
        Console.WriteLine($"Estado del hilo después de arrancar: {worker.ThreadState}");

        // Dormimos el hilo principal un poco para que el hilo worker tenga tiempo de currar
        Thread.Sleep(100);

        Console.WriteLine($"Estado del hilo (más tarde): {worker.ThreadState}");

        // Esperamos a que worker termine (nos unimos)
        worker.Join();

        Console.WriteLine($"Estado del hilo después de terminar: {worker.ThreadState}");
        Console.WriteLine("El hilo principal ha terminado");
    }

    static void DoWork()
    {
        Console.WriteLine("¡El hilo de trabajo ha empezado a trabajar!");
        Thread.Sleep(500);
        Console.WriteLine("¡El hilo de trabajo ha terminado!");
    }
}

¿Qué muestra el programa?

  1. Después de crear el hilo — el estado será Unstarted.
  2. Tras arrancarlo — normalmente será inmediatamente Running (pero puede aparecer como Running | Background).
  3. Mientras trabaja — el estado puede ser Running, o WaitSleepJoin si el hilo está "durmiendo".
  4. Al terminar el método — el estado pasa a Stopped.

Este código es una gran herramienta para entender en qué estado puede estar tu hilo. Puedes jugar con los delays y ver cómo cambia el estado.

3. Gestión del hilo: métodos principales

Arranque: Start()

Obvio, pero lo repetimos: creas un hilo — arráncalo con Start(). Y ojo, se puede arrancar solo una vez: intentar llamar a Start() de nuevo lanzará una excepción ThreadStateException.

Thread t = new Thread(MyMethod);
t.Start();   // OK
t.Start();   // ¡Error!

Esperar a que termine: Join()

A veces necesitas esperar a que un hilo acabe todo y luego continuar. Para eso está Join().

Thread t = new Thread(MyMethod);
t.Start();
t.Join(); // Bloquea el hilo actual hasta que t termine

Si tienes varios hilos, puedes llamar Join() para cada uno — el hilo principal esperará hasta que todos los "currantes" terminen.

Variantes: existe la sobrecarga Join(int millisecondsTimeout), que espera solo el tiempo indicado y luego continúa.

// Esperamos como máximo 2 segundos
if (t.Join(2000))
    Console.WriteLine("El hilo terminó a tiempo");
else
    Console.WriteLine("Ya estamos hartos de esperar...");

Parada forzada: por qué es mala idea

En versiones antiguas de .NET existía el método Thread.Abort(), que permitía "matar" un hilo al vuelo. Hoy en día casi no se usa — es peligroso y puede dejar la aplicación en un estado raro. La filosofía de .NET es: el hilo debe terminar voluntariamente. No "matas" al empleado — le das a entender educadamente que la jornada ha acabado.

4. Cómo "parar" correctamente un hilo

La forma más correcta y segura de detener el trabajo de un hilo es usar una bandera de cancelación o un indicador de finalización que el propio hilo compruebe periódicamente.

class Worker
{
    private volatile bool shouldStop = false;

    public void DoWork()
    {
        while (!shouldStop)
        {
            Console.WriteLine("¡Estoy trabajando!");
            Thread.Sleep(300);
        }

        Console.WriteLine("El hilo termina su trabajo por petición.");
    }

    public void RequestStop()
    {
        shouldStop = true;
    }
}

Uso:

Worker w = new Worker();
Thread t = new Thread(w.DoWork);
t.Start();

// Esperamos un poco
Thread.Sleep(1000);

// Pedimos al hilo que termine
w.RequestStop();
t.Join(); // Esperamos a que el hilo termine

Punto importante: volatile

La palabra clave volatile le dice al compilador y al procesador: "¡No caches este campo, toma siempre el valor más reciente!" Esto es importante para que el hilo vea el indicador de parada actualizado. Sin esto (o sin otros mecanismos de sincronización) el hilo podría no ver nunca tus cambios.

5. Transición de hilos a estados de espera y sueño

A veces un hilo deja de trabajar temporalmente — está o esperando o durmiendo.

Sueño: Thread.Sleep

Cuando quieres dar un respiro al hilo o ralentizar la ejecución (por ejemplo, para no saturar la CPU), usa Thread.Sleep(milliseconds).

// El hilo duerme 2 segundos
Thread.Sleep(2000);

Mientras duerme, el hilo no hace trabajo alguno.

Espera / Join

Cuando el hilo principal espera a que uno hijo termine (Join), el principal está "en pausa". De forma similar, si un hilo espera la liberación de un recurso (por ejemplo, con monitores u otros primitivos de sincronización), entra en un estado especial de espera.

6. Gestión de si un hilo es background

En .NET los hilos son de dos tipos: foreground (primer plano) y background (segundo plano). La diferencia es simple:

  • Si en el proceso solo quedan hilos background, el proceso terminará automáticamente.
  • El hilo principal y todos los hilos foreground deben terminar para que el proceso se detenga.

Puedes indicar explícitamente que un hilo sea background:

Thread t = new Thread(SomeMethod);
t.IsBackground = true; // Marcado como background
t.Start();

Ejemplo práctico — Demonio vs. hilo normal

Thread t = new Thread(() =>
{
    while (true)
    {
        Console.WriteLine("¡Soy un fantasma (background), no me vas a parar!");
        Thread.Sleep(500);
    }
});
t.IsBackground = true; // Lo hacemos background
t.Start();

Thread.Sleep(1200);
Console.WriteLine("El hilo principal termina");
// Al acabar Main — el proceso muere, y nuestro hilo eterno también desaparece

Al finalizar Main — el proceso se termina; los hilos background se detienen automáticamente.

7. Detalles útiles

Qué no hacer con los hilos

  • No reinventes el hilo. El objeto Thread vive una sola vez: cuando su método termina — el hilo murió, y intentar llamar a Start() otra vez lanzará una excepción.
  • No intentes detener forzosamente hilos ajenos con métodos como Thread.Abort() o Thread.Suspend() — están obsoletos y son peligrosos.
  • No ignores el cierre correcto del trabajo del hilo. Si un hilo usa archivos o recursos, asegúrate de liberarlos correctamente antes de que el hilo termine.

Comprobar estado y gestionar el ciclo de vida

if (t.IsAlive)
{
    Console.WriteLine("El hilo sigue vivo");
}
else
{
    Console.WriteLine("El hilo ha terminado");
}

IsAlive — es true mientras el hilo ejecuta su método; tras finalizar — es false.

Ciclo de vida de un hilo simple en .NET

Estado Cómo entrar ¿Qué significa? Cómo salir
Unstarted
new Thread(...)
Hilo creado, no arrancado Llamar a Start()
Running
Start()
El hilo está ejecutando trabajo Finalizar el método
WaitSleepJoin Sleep(), Join(), espera El hilo está temporalmente inactivo Se termina la espera
Stopped Método del hilo finalizado El hilo "murió" No sale — fin

Preguntas frecuentes sobre la gestión del ciclo de vida del hilo

Pregunta: ¿Se puede matar un hilo por comando?
Respuesta: No y no deberías; los hilos deben vigilar por sí mismos el fin de la ejecución. Usa banderas de cancelación.

Pregunta: ¿Se puede reutilizar el objeto Thread?
Respuesta: No. Crea un nuevo objeto para cada trabajo nuevo.

Pregunta: ¿Qué pasa si el hilo principal termina y el hijo sigue trabajando?
Respuesta: Si el hilo hijo es background (IsBackground == true), la aplicación terminará. Si no — el proceso seguirá vivo hasta que todos los hilos terminen.

Pregunta: ¿Cómo limpiar recursos correctamente si un hilo termina por cancelación?
Respuesta: Usa bloques try...finally dentro del método del hilo para asegurar la liberación de recursos en cualquier caso.

8. Errores típicos y cómo evitarlos al trabajar con hilos

Error nº1: reutilizar el mismo objeto Thread.
No puedes arrancar el mismo objeto de hilo más de una vez. Tras finalizar el hilo no se puede reiniciar — eso provocará una excepción.

Error nº2: liberar mal recursos externos desde un hilo.
Si el hilo trabaja con ficheros, red o recursos externos, asegúrate de cerrarlos y liberarlos correctamente. Se recomienda usar bloques finally o la construcción using para evitar fugas y bloqueos.

Error nº3: crear demasiados hilos.
Un número excesivo de hilos complica la depuración y puede reducir el rendimiento. Un hilo de más es tiempo perdido buscando y arreglando bugs inesperados.

2
Tarea
C# SELF, nivel 55, lección 2
Bloqueada
Estados principales de un hilo
Estados principales de un hilo
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION