Concurrencia y subprocesamiento múltiple en C#: desde Thread hasta async/await
Las aplicaciones modernas requieren cada vez más la ejecución simultánea de varias tareas: desde el procesamiento de solicitudes de un servidor web hasta cálculos paralelos en programas de escritorio. En C# (.NET) existe un potente arsenal de herramientas para esto, desde hilos clásicos (Thread) hasta construcciones de alto nivel como async/await y la biblioteca Task Parallel Library (TPL). En este artículo, analizaremos los conceptos clave de concurrencia y subprocesamiento múltiple, su aplicación práctica y las mejores prácticas.
¿Qué es concurrencia y subprocesamiento múltiple?
Antes de pasar al código, es importante entender la diferencia entre estos conceptos:
- Concurrencia (Concurrency) — la capacidad de un programa para ejecutar varias tareas, alternando entre ellas, creando la ilusión de ejecución simultánea. Esto se puede implementar incluso en un núcleo de procesador.
- Subprocesamiento múltiple (Multithreading) — una forma de concurrencia en la que varios hilos (threads) se ejecutan físicamente al mismo tiempo en un procesador multinúcleo.
- Paralelismo (Parallelism) — un subconjunto del subprocesamiento múltiple, donde diferentes partes de una tarea se ejecutan estrictamente al mismo tiempo en diferentes núcleos.
En C#, puede usar tanto hilos de bajo nivel como abstracciones que gestionan los hilos automáticamente. La elección depende de la tarea: para operaciones I/O-bound (trabajo con archivos, red) es mejor usar async/await, y para CPU-bound (cálculos) — Parallel.For o Task.
Creación y gestión de hilos: clase Thread
La clase System.Threading.Thread es la forma más baja de trabajar con hilos en C#. Proporciona control total, pero requiere una sincronización cuidadosa.
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 termine el hilo secundario Console.WriteLine("Trabajo completado."); }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Hilo secundario: {i}"); Thread.Sleep(150); } }}Problemas de la gestión manual de hilos:
- Complejidad de sincronización (condiciones de carrera, deadlocks).
- Alto costo general de creación y destrucción de hilos.
- Falta de soporte integrado para devolver un resultado.
Por lo tanto, en el C# moderno se prefiere usar Task Parallel Library.
Task Parallel Library (TPL) y async/await
La clase Task del espacio de nombres System.Threading.Tasks representa una operación asíncrona. En combinación con las palabras clave async y await, permite escribir código asíncrono que parece síncrono.
Ejemplo básico con Task
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Console.WriteLine("Inicio del trabajo.");
// Iniciamos una tarea asíncrona Task<int> task = CalculateAsync(10);
// Mientras la tarea se ejecuta, podemos hacer otra cosa Console.WriteLine("Haciendo otro trabajo...");
// Esperamos el resultado int result = await task; Console.WriteLine($"Resultado: {result}"); }
static async Task<int> CalculateAsync(int number) { await Task.Delay(1000); // Simulación de una operación larga return number * number; }}