CodeGym /Cursos /C# SELF /Arrancar hilos usando la clase

Arrancar hilos usando la clase Thread

C# SELF
Nivel 55 , Lección 1
Disponible

1. Introducción

Si imaginas un proceso como un supermercado, los hilos (thread) son los cajeros en distintas cajas que atienden clientes simultáneamente. Todos los cajeros trabajan en la misma tienda, pero cada uno hace su tarea en paralelo, por eso el trabajo va más rápido y eficiente. Los hilos dentro de un proceso permiten ejecutar varias tareas a la vez, compartiendo recursos comunes y coordinando el trabajo dentro de una sola aplicación. La principal ventaja: los hilos pueden realmente ejecutarse en paralelo si el procesador soporta multitarea.

Necesidad práctica de hilos

¿Para qué arrancar varios hilos? Aquí tienes un par de situaciones reales:

  • Estás escribiendo una aplicación con interfaz gráfica y no quieres que se "congele" durante una operación larga.
  • Necesitas descargar varios archivos al mismo tiempo.
  • En un juego, los enemigos deben pensar por su cuenta independientemente de los demás.

Curiosamente, en muchos programas todavía aparecen "congelamientos" por manejo inexperto de hilos. Hoy aprenderemos a evitar esos líos.

La clase Thread: la base del multihilo manual

La clase Thread es un dinosaurio dentro de la programación multihilo en .NET. A pesar de herramientas más modernas (Task, async/await), trabajar con Thread sigue teniendo sentido, especialmente si quieres sentirte como el dueño de los hilos "desde cero".

Esquema para crear un hilo

  1. Crear un objeto de la clase Thread, pasando el método que se ejecutará en el hilo.
  2. Arrancar el hilo con el método Start().
  3. (Opcional) Ver qué ocurre — ¡todo puede ejecutarse en paralelo!

2. Arrancar un hilo con Thread

Probemos sentir la magia del paralelismo. Añadamos a nuestra aplicación un pequeño clase que cuente hasta un número y muestre el progreso. Aprenderemos a ejecutar ese trabajo en un hilo aparte.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("¡El hilo principal ha arrancado!");

        // Creamos el objeto Thread, indicando el método a ejecutar
        Thread workerThread = new Thread(CountToTen);

        // Arrancamos el nuevo hilo
        workerThread.Start();

        // El hilo principal también hace algo: escribe puntos...
        for (int i = 0; i < 5; i++)
        {
            Console.Write(".");
            Thread.Sleep(500); // Retardo para que se vea
        }

        Console.WriteLine("\n¡El hilo principal ha terminado!");
    }

    static void CountToTen()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Hilo] Contando: {i}");
            Thread.Sleep(400);
        }
        Console.WriteLine("[Hilo] ¡Listo!");
    }
}

¿Qué pasa?

Verás en la consola que los puntos y "Contando: X" aparecen mezclados. ¡Ese es el primer signo de multihilo! El hilo principal escribe sus puntos, y el nuevo hilo cuenta hasta 10. No se estorban, como dos músicos en una banda: uno toca la batería y el otro el piano. Cada uno suena a su manera y juntos hacen música.

3. ¿Cómo pasar datos al hilo?

A veces el hilo debe saber no solo qué hacer, sino con qué trabajar. Si el método del hilo acepta parámetros, ¿cómo se los pasamos?

Opción 1: Usando una lambda (método anónimo)

int bounds = 7;
Thread t = new Thread(() => CountToNumber(bounds));
t.Start();
static void CountToNumber(int n)
{
    for (int i = 1; i <= n; i++)
    {
        Console.WriteLine($"[Hilo] {i} / {n}");
        Thread.Sleep(300);
    }
}

Aquí envolvemos la llamada al método en una lambda para pasar parámetros. Es una práctica muy común, porque Thread espera un método sin parámetros (ThreadStart).

Opción 2: Usar ParameterizedThreadStart

Puedes usar el delegado especial ParameterizedThreadStart, que acepta un parámetro tipo object.

Thread t = new Thread(CountToNumberObject);
t.Start(12);

static void CountToNumberObject(object? n)
{
    int max = (int)n!;
    for (int i = 1; i <= max; i++)
    {
        Console.WriteLine($"[Hilo] {i} / {max}");
        Thread.Sleep(200);
    }
}

Sí, el tipo del parámetro es object, así que hace falta un cast. No es ideal, pero funciona. En C# moderno se prefiere la opción de la lambda.

4. Gestionar la vida del hilo

Veamos qué herramientas útiles ofrece la clase Thread.

Propiedad / Método Propósito
Thread.Start()
Arranca el hilo (el método indicado al crear el hilo)
Thread.Join()
Espera a que termine el hilo (bloquea el hilo que llama hasta que el otro termina)
Thread.IsAlive
Indica si el hilo está corriendo ahora (true/false)
Thread.Name
Permite dar un nombre al hilo (útil para depuración)
Thread.CurrentThread
Obtener el objeto que representa el hilo actual
Thread.Sleep(ms)
Detiene el hilo actual durante ms milisegundos

Ejemplo: Esperar a que termine un hilo

A veces el hilo principal necesita esperar a que termine un hilo auxiliar.

Thread t = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"[Segundo hilo] {i}");
        Thread.Sleep(300);
    }
});
t.Start();

Console.WriteLine("[Hilo principal] Esperando a que termine el segundo hilo...");
t.Join(); // El hilo principal espera aquí

Console.WriteLine("[Hilo principal] ¡El segundo hilo ha terminado!");

Sin Join() el programa podría terminar incluso si el hilo sigue trabajando. Con Join() el hilo principal espera pacientemente a que todo acabe.

5. Matices útiles

Nombrar hilos: para no liarse

Para depuración puedes poner nombres a los hilos:

Thread t = new Thread(() =>
{
    Console.WriteLine($"Esto se ejecuta en el hilo: {Thread.CurrentThread.Name}");
});
t.Name = "Hilo-Contador";
t.Start();

Esto ayuda cuando hay muchos hilos y cada uno hace su propia tarea.

Limitaciones y futuro real

Hay que decirlo: en aplicaciones modernas, gestionar hilos manualmente con Thread es raro. En la práctica se suelen usar herramientas más potentes e inteligentes (Task, async/await), que veremos más adelante. Pero entender cómo funcionan los hilos es importante para:

  • Entender el "detrás de cámaras" de C# y .NET.
  • Prepararse para entrevistas (a veces te pedirán explicar la diferencia entre Thread y Task).
  • Diagnosticar y arreglar problemas complejos en aplicaciones grandes y heredadas.

Esquema final: ciclo de vida del hilo

stateDiagram-v2
    [*] --> New: Thread creado
    New --> Running: Start()
    Running --> Stopped: Método terminado
    Stopped --> [*]

Ahora sabes crear y arrancar hilos en C# por tu cuenta. Ya no eres solo un pasajero en el tren, eres el maquinista que controla varios vagones a la vez. Por delante: ciclo de vida del hilo, su gestión, sincronización y nuevos horizontes del paralelismo.

6. Errores típicos y trucos al trabajar con Thread

Error nº1: no arrancar el hilo.
A menudo se crea un objeto Thread pero se olvida llamar a Start(). Como resultado el hilo no comienza a trabajar, y puede ser difícil entender por qué.

Error nº2: modificar datos compartidos sin sincronización.
Si varios hilos trabajan con la misma variable sin protección, ¡espera problemas! Es como si dos cajeros dieran cambio desde la misma caja: pronto habrá confusión y errores.

Error nº3: usar métodos obsoletos y peligrosos.
No uses Thread.Suspend(), Thread.Resume() y similares — son peligrosos y obsoletos. Gestiona el ciclo de vida de los hilos con otras técnicas.

Error nº4: excepciones no capturadas dentro de hilos.
Si en un hilo ocurre una excepción no capturada, el hilo terminará y el hilo principal quizá ni se entere. Envuelve el código del hilo en un bloque try-catch para capturar errores y registrarlos.

Thread t = new Thread(() =>
{
    try
    {
        // ... tu código
    }
    catch (Exception ex)
    {
        // Registramos el error
        Console.WriteLine($"[Hilo] Error: {ex.Message}");
    }
});
t.Start();
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION