1. Introducción
La multihilo es como el trabajo paralelo de varios empleados en una oficina: uno imprime documentos, otro llama al cliente, el tercero hace café (y, claro, todos son programadores). Si tuvieras sólo un empleado, haría todo en serie, y la oficina se ahogaría en aburrimiento y cola por el café. En programación la situación es análoga: un programa single-threaded sólo puede ejecutar una tarea a la vez.
Imagina que nuestra aplicación realiza una operación larga, por ejemplo, descarga un archivo de internet o calcula una tabla enorme. Todo lo demás en ese momento se "congela": los botones no responden, la animación no se mueve, aparecen mensajes "no responde" en ventanas emergentes.
La multihilo permite a la aplicación hacer varias cosas a la vez: la interfaz sigue siendo reactiva, las operaciones se ejecutan en paralelo, y no acabamos en un monólogo tipo "¡Ordenador, otra vez te has quedado colgado!".
Conceptos y terminología básicos
Antes de zambullirnos, aclaremos qué es un hilo (thread) y en qué difiere de un proceso.
- Proceso (Process): Programa independiente con su propio espacio de direcciones, variables y recursos. Por ejemplo, cada aplicación abierta en Windows es un proceso separado.
- Hilo (Thread): Unidad de ejecución dentro de un proceso. Un proceso puede contener uno o varios hilos que trabajan con los mismos recursos (memoria, variables).
¿Por qué la multihilo genera tantas preguntas?
Porque los hilos son tipos impredecibles: pueden empezar a ejecutarse en cualquier momento, mezclar datos, interrumpirse entre sí y en general montar un desastre en la memoria si no controlas el orden. Si te parece que esto se parece a una guardería sin monitor — ¡exacto! Disciplina y cuidado con los hilos son la base para escribir programas multihilo fiables.
2. Historia y papel de la multihilo en C# y .NET
En tiempos lejanos C# era básicamente single-threaded y los programas eran sencillos. Con el aumento de las demandas de rendimiento, la aparición de CPUs multinúcleo y la necesidad de crear aplicaciones reactivas sin bloqueos de UI, .NET recibió medios para multihilo. Al principio estuvo la clásica System.Threading.Thread, luego llegaron las Tasks (Task), los métodos asíncronos (async/await), el procesamiento paralelo de datos (PLINQ) y primitivas de sincronización de alto nivel.
C# creció hasta ser una plataforma potente donde la multihilo no es una rareza, sino el pan de cada día.
Visualmente: proceso y hilos
Aquí tienes un esquema simple:
+--------------------------------------------------+
| Proceso (tu programa) |
| +-------------+ +-------------+ |
| | Hilo 1 | | Hilo 2 | |
| +-------------+ +-------------+ |
| ... |
| +-------------+ |
| | Hilo N | |
| +-------------+ |
+--------------------------------------------------+
Todos los hilos dentro del proceso ven las variables y recursos compartidos.
3. ¿Cómo crear un hilo en C#?
Empezamos por lo más básico: la clase Thread del namespace System.Threading.
Ejemplo: lanzamos un segundo hilo
Tengamos una tarea larga — por ejemplo, contar la suma de números del 1 al 10_000_000. Mientras cuenta, el hilo principal muestra un saludo al usuario.
using System;
using System.Threading;
class Program
{
// Método para la segunda tarea
static void CalculateSum()
{
long sum = 0;
for (int i = 1; i <= 10_000_000; i++)
sum += i;
Console.WriteLine($"[Hilo 2] Suma: {sum}");
}
static void Main()
{
// Creamos el hilo, pasando el delegado al método
Thread thread = new Thread(CalculateSum);
thread.Start(); // Arrancamos el segundo hilo
// El hilo principal sigue trabajando
Console.WriteLine("[Hilo 1] ¡Hola! Trabajando en paralelo...");
// Esperamos la finalización del segundo hilo antes de salir
thread.Join();
Console.WriteLine("[Hilo 1] ¡Todo terminado!");
}
}
¿Qué ocurrirá?
En la pantalla aparecerá la línea "[Hilo 1] ¡Hola! Trabajando en paralelo...", y luego, cuando el hilo que calcula termine, mostrará la suma.
Problema típico: el que llega primero imprime
Prueba ejecutar este código varias veces — ¡el orden de las líneas puede variar! A veces la suma sale primero, otras el saludo. Eso es la auténtica multihilo: los programas se vuelven menos predecibles, como el humor de un gato los lunes.
4. Zona de memoria: ¿qué ven los hilos?
Todos los hilos dentro de un proceso tienen acceso a las mismas variables (si no son locales del método). Si un hilo cambia una variable — ¡los demás la verán!
Ejemplo: variable compartida
using System;
using System.Threading;
class Program
{
static int counter = 0;
static void Increment()
{
for (int i = 0; i < 1000; i++)
counter++;
}
static void Main()
{
Thread t1 = new Thread(Increment);
Thread t2 = new Thread(Increment);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"counter = {counter}");
}
}
¿Qué esperamos ver en counter? La lógica sugiere 2000, porque cada hilo incrementa 1000 veces.
¡Pero no! Ejecuta varias veces y verás valores distintos: 1782, 1935, 1999…
¿Por qué? Es el clásico problema de Race Condition: los hilos se "roban" el control entre lectura -> incremento -> escritura, y algunos incrementos se pierden.
5. Nociones útiles
¿Cómo interactúan los hilos con la interfaz?
En aplicaciones desktop modernas (WinForms/WPF/MAUI) el hilo principal atiende la UI. Todas las acciones del usuario (clicks, input) están en ese hilo. Las tareas en background deben ejecutarse en otros hilos, pero — por norma — no se debe "tocar" la UI desde hilos distintos. Esto evita el caos.
En consola no hay esa restricción, se puede escribir con Console.WriteLine desde cualquier hilo. Sin embargo, en aplicaciones reales sin la sincronización adecuada la UI puede comportarse raro.
Secuencia y paralelismo
Tabla para fijar las diferencias.
| Código single-thread | Código multihilo |
|---|---|
| Ejecuta tareas en serie | Las tareas pueden ejecutarse simultáneamente |
| La UI "se cuelga" con tareas largas | La UI sigue siendo reactiva |
| Leer y escribir variables es sencillo | Requiere control de acceso a datos |
| Fácil de depurar | Puede ser difícil de depurar |
Puntos importantes al trabajar con hilos
- Variables compartidas = riesgo compartido. Como mostramos arriba, si varios hilos usan una variable compartida — sin sincronización pueden ocurrir errores (más detalles en próximas lecciones).
- Puedes "esperar" un hilo con Join(). El método Join() permite suspender el hilo principal hasta que el background termine. Úsalo si necesitas esperar un resultado.
- Un hilo no se puede arrancar dos veces. Después de ejecutarse, no puedes volver a Start() el mismo Thread — hay que crear un nuevo objeto Thread.
- Finalización de un hilo. Un hilo termina cuando su método acaba. Matar hilos forzosamente es mala idea (el método Abort() se considera peligroso y obsoleto).
¿Para qué sirve la multihilo en la práctica?
- Aplicaciones UI: no congelar la interfaz cuando hay cargas o cálculos en segundo plano.
- Servidores y servicios: procesar simultáneamente peticiones de muchos clientes.
- Cómputo de alto rendimiento: dividir una tarea grande (por ejemplo, procesar millones de registros) en partes y ejecutarlas en paralelo.
- Juegos, simulaciones, procesamiento de datos: modelar sistemas complejos sin perder rendimiento.
Problemas de la multihilo
La multihilo da poder, pero añade complejidad:
- Race Condition (condición de carrera): cuando varios hilos cambian los mismos datos, el resultado depende del orden de instrucciones, que es impredecible.
- Deadlock (bloqueo mutuo): los hilos se esperan entre sí y nadie puede continuar.
- Starvation (injusto/retraso): uno de los hilos queda constantemente privado de acceso al recurso.
En esta lección sólo mencionamos los problemas principales de la multihilo; en las siguientes aprenderemos a reconocerlos y evitarlos. De momento — recuerda que si todo "se queda colgado", la culpa puede ser no sólo un bug, sino también hilos que organizaron una fiesta.
GO TO FULL VERSION