Concurrencia y Multihilo en C#: Una Guía Completa
Las aplicaciones modernas requieren alto rendimiento y capacidad de respuesta. La concurrencia y el multihilo en C# son herramientas clave que permiten ejecutar múltiples tareas simultáneamente, utilizar eficientemente procesadores multi-núcleo y mejorar la experiencia del usuario. En este artículo, desglosaremos los conceptos fundamentales, comenzando desde los hilos clásicos hasta los modernos async/await y Parallel LINQ.
Fundamentos del Multihilo: La Clase Thread y el Thread Pool
El multihilo en C# comienza con la clase System.Threading.Thread. Cada hilo representa una ruta de ejecución separada dentro de un proceso. Sin embargo, crear hilos manualmente es una operación costosa. Para optimizar, se utiliza el pool de hilos (ThreadPool), que gestiona la reutilización de hilos.
Ejemplo de creación manual de un hilo:
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start(); Console.WriteLine("El hilo principal está trabajando..."); thread.Join(); // Esperar a que termine }
static void DoWork() { Console.WriteLine("El hilo está realizando trabajo."); }}Para un uso más simple, se recomienda ThreadPool:
ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("Tarea en el pool de hilos.");});Importante: La clase Thread tiene una API de bajo nivel y no soporta devolver un resultado. Para aplicaciones modernas, use Task.
Task y TPL (Task Parallel Library)
La librería TPL (System.Threading.Tasks) proporciona una abstracción de alto nivel — Task. Una Task representa una operación asíncrona que puede devolver un valor. Esta es la base para la concurrencia moderna en C#.
Ejemplo de una Task sin valor de retorno:
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Task task = Task.Run(() => DoWork()); await task; Console.WriteLine("Completado."); }
static void DoWork() { Console.WriteLine("Trabajo en Task."); }}Task con valor de retorno:
Task<int> task = Task.Run(() => { int result = 0; for (int i = 0; i < 1000000; i++) result += i; return result;});int sum = await task;Console.WriteLine($"Suma: {sum}");TPL también incluye Parallel.For y Parallel.ForEach para procesamiento paralelo de colecciones:
Parallel.For(0, 10, i => { Console.WriteLine($"Iteración {i} en el hilo {Thread.CurrentThread.ManagedThreadId}");});Async/await: Programación Asíncrona
Las palabras clave async y await simplifican la escritura de código asíncrono, haciéndolo parecer código síncrono. No crean nuevos hilos, sino que cambian eficientemente el contexto de ejecución, liberando el hilo para otras tareas.
Ejemplo de un método asíncrono:
using System;using System.Net.Http;using System.Threading.Tasks;
class Program{ static async Task Main() { string data = await FetchDataAsync("https://example.com"); Console.WriteLine(data); }
static async Task<string> FetchDataAsync(string url) { using HttpClient client = new HttpClient(); string result = await client.GetStringAsync(url); return result; }}Reglas de uso:
- Un método con
asyncdebe devolverTask,Task<T>ovoid(solo para manejadores de eventos