Introducción: ¿por qué se necesita concurrencia en C#?
Las aplicaciones modernas requieren cada vez más la ejecución de varias operaciones simultáneamente. El usuario espera que la interfaz no se "congele" al cargar datos, y que el servidor pueda procesar miles de solicitudes en paralelo. En C# (.NET), para resolver estos problemas se utiliza la concurrencia — la capacidad de un programa para ejecutar varias tareas en intervalos de tiempo superpuestos — y el subprocesamiento múltiple — la implementación de la concurrencia mediante múltiples hilos de ejecución.
En este artículo, analizaremos los conceptos clave: desde los Thread básicos hasta los modernos async/await y Parallel. Aprenderá a elegir la herramienta adecuada para cada tarea y a evitar errores típicos como los bloqueos mutuos (deadlocks) y las condiciones de carrera (race conditions).
1. Fundamentos: Thread y ThreadPool
Trabajo con hilos mediante Thread
La clase System.Threading.Thread es la forma de más bajo nivel para crear hilos. Cada hilo tiene su propia pila y se ejecuta de forma independiente. Ejemplo de creación e inicio de un hilo:
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start(); // El hilo principal continúa su trabajo for (int i = 0; i < 5; i++) { Console.WriteLine($"Hilo principal: {i}"); Thread.Sleep(100); } thread.Join(); // Esperamos a que finalice el hilo secundario }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Hilo de trabajo: {i}"); Thread.Sleep(150); } }}Importante: Crear un nuevo hilo es una operación costosa. Para la creación frecuente de hilos de corta duración, es mejor usar ThreadPool.
ThreadPool — grupo de hilos
ThreadPool gestiona un conjunto de hilos de trabajo que se reutilizan. Esto es más eficiente que crear un Thread manualmente. Ejemplo:
ThreadPool.QueueUserWorkItem(state =>{ Console.WriteLine("La tarea se ejecuta en el grupo de hilos"); Thread.Sleep(500); Console.WriteLine("Tarea completada");});
Console.WriteLine("El hilo principal continúa su trabajo");Console.ReadKey();Limitación: No puede controlar directamente la prioridad o el nombre del hilo del grupo. Para un control más preciso, use Task.
2. Task y Task Parallel Library (TPL)
Task — el enfoque moderno
A partir de .NET 4.0, Task se convirtió en la herramienta principal para operaciones asíncronas. Abstrae los hilos y permite trabajar con los resultados de la ejecución. Ejemplo:
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Task<int> task = Task.Run(() => ComputeResult(10)); // Se puede hacer otro trabajo Console.WriteLine("Esperando el resultado..."); int result = await task; Console.WriteLine($"Resultado: {result}"); }
static int ComputeResult(int value) { Task.Delay(1000).Wait(); // Simulación de una operación larga return value * 2; }}Ventajas clave:
- Devuelve un resultado mediante
Task<T> - Admite cancelación mediante
CancellationToken - Permite combinar tareas (ContinueWith, WhenAll, WhenAny)
- Integrado con
async/await
Parallel — para bucles paralelos
La clase Parallel simplifica la ejecución de bucles con paralelización. Ejemplo:
using System;using System.Threading.Tasks;
class Program{ static void Main()\