Concurrencia y subprocesamiento múltiple en C#: nivel avanzado

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Concurrencia y subprocesamiento múltiple en C#: nivel avanzado

\n\n

En el desarrollo moderno con C# (.NET), la capacidad de gestionar eficazmente la concurrencia y el subprocesamiento múltiple distingue a un ingeniero experimentado de un principiante. El conocimiento básico de Thread y los bloqueos simples ya no es suficiente para crear aplicaciones de alto rendimiento, receptivas y escalables. En este artículo, analizaremos técnicas avanzadas: desde una comprensión profunda de async/await hasta el uso de System.Threading.Channels y TPL Dataflow. El material está dirigido a desarrolladores que ya conocen los fundamentos del subprocesamiento múltiple.

\n\n

1. Inmersión profunda en async/await y SynchronizationContext

\n\n

Muchos desarrolladores usan async/await, pero no comprenden completamente cómo el programador de tareas (Task Scheduler) y SynchronizationContext gestionan las continuaciones. A nivel avanzado, es importante controlar en qué subproceso se ejecuta el código después de await.

\n\n

Por defecto, await captura el SynchronizationContext actual (o TaskScheduler). En aplicaciones de UI (WPF, WinForms), esto devuelve la ejecución al subproceso principal. En ASP.NET Core y aplicaciones de consola, el contexto a menudo está ausente y la continuación puede ejecutarse en cualquier subproceso del grupo.

\n\n

Para un ajuste fino, use ConfigureAwait(false). Esto le indica al entorno de ejecución que no intente volver al contexto original, lo que mejora el rendimiento y previene interbloqueos (deadlocks) en el código de bibliotecas.

\n\n
public async Task<string> FetchDataAsync(HttpClient client)\n{\n    // La continuación no capturará el contexto\n    var result = await client.GetStringAsync("https://api.example.com")\n                              .ConfigureAwait(false);\n    // Este código se ejecutará en cualquier subproceso del grupo\n    return ProcessResult(result);\n}\n\npublic async Task<string> FetchDataWithContextAsync(HttpClient client)\n{\n    // La continuación volverá al contexto original (por ejemplo, subproceso de UI)\n    var result = await client.GetStringAsync("https://api.example.com");\n    // Este código se ejecutará garantizadamente en el contexto original\n    return ProcessResult(result);\n}
\n\n

Consejo avanzado: Cree sus propios SynchronizationContext para escenarios no estándar, por ejemplo, para organizar una cola de tareas con un número fijo de subprocesos.

\n\n

2. Task Parallel Library (TPL) y paralelismo de datos

\n\n

TPL proporciona abstracciones de alto nivel para el paralelismo. Parallel.For y Parallel.ForEach dividen automáticamente las colecciones en partes (chunks) y las distribuyen entre los subprocesos. Sin embargo, a nivel avanzado, es necesario comprender cómo configurar el grado de paralelismo y el manejo de excepciones.

\n\n

Use ParallelOptions para limitar el número de subprocesos, así como ParallelLoopState para salir anticipadamente del bucle.

\n\n
var options = new ParallelOptions\n{\n    MaxDegreeOfParallelism = Environment.ProcessorCount / 2 // Usamos la mitad de los núcleos\n};\n\nParallel.ForEach(sourceData, options, (item, state) =>\n{\n    try\n    {\n        // Operación pesada\n        ProcessItem(item);\n    }\n    catch (Exception ex)\n    {\n        // Registramos y detenemos la ejecución\n        Console.WriteLine($"Error: {ex.Message}");\n        state.Stop(); // Detiene la ejecución del bucle\n    }\n});
\n\n

Importante: Parallel.ForEach no garantiza el orden de procesamiento. Si el orden es importante, use Parallel.ForEach con índice o PLINQ (AsParallel().AsOrdered()).

\n\n

3. Canales (System.Threading.Channels) para Productor-Consumidor

\n\n

La clase Channel<T> apareció en .NET Core 3.0 y se ha convertido en una herramienta poderosa para escenarios de productor-consumidor. Es segura para subprocesos, asíncrona y eficiente. Los canales son

Blogs

Book Recommendations