Gestión de procesos y subprocesos en C#
Aprende gestión de procesos y subprocesos en C# para controlar la ejecución y los recursos del sistema.
En las aplicaciones modernas, los conceptos de proceso y hilo (thread) se utilizan para ejecutar varias tareas al mismo tiempo. C# y .NET ofrecen potentes API para administrar estas dos estructuras. Un proceso es una instancia de un programa en ejecución, mientras que un hilo es un flujo de ejecución paralelo dentro de ese proceso. Este artículo explica los fundamentos de iniciar procesos, administrar hilos, sincronización y ejecución paralela.
¿Qué es un Proceso?
Cada aplicación en ejecución es representada por el sistema operativo como un proceso.
Un proceso tiene su propio espacio de memoria, recursos y hilos activos.
En .NET, la gestión de procesos se realiza mediante la clase System.Diagnostics.Process.
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
// Iniciar un nuevo proceso de Bloc de notas
Process.Start("notepad.exe");
// Listar todos los procesos en ejecución
foreach (var proc in Process.GetProcesses())
{
Console.WriteLine($"{proc.ProcessName} - ID: {proc.Id}");
}
}
}
El método Process.Start() inicia una nueva aplicación,
mientras que GetProcesses() obtiene todos los procesos que se están ejecutando actualmente.
Trabajar con un Proceso
Puedes acceder, cerrar o pausar una aplicación en ejecución a través de su objeto de proceso.
using System;
using System.Diagnostics;
using System.Threading;
class Program
{
static void Main()
{
// Iniciar el Bloc de notas
var p = Process.Start("notepad.exe");
// Esperar 3 segundos
Thread.Sleep(3000);
// Cerrar
p.Kill();
Console.WriteLine("Bloc de notas cerrado.");
}
}
En este ejemplo, el proceso se termina después de 3 segundos.
Atención: el método Kill() finaliza el proceso de forma forzada,
lo que puede causar pérdida de datos no guardados.
¿Qué es un Hilo (Thread)?
Un hilo es una unidad de ejecución dentro de un proceso. Por defecto, toda aplicación comienza con un hilo principal. Se pueden crear hilos adicionales para ejecutar diferentes tareas en paralelo.
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread t = new Thread(Saludar);
t.Start();
for (int i = 0; i < 3; i++)
{
Console.WriteLine("El hilo principal está en ejecución...");
Thread.Sleep(500);
}
t.Join(); // Esperar a que el hilo termine
Console.WriteLine("Programa finalizado.");
}
static void Saludar()
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("El hilo de saludo está en ejecución...");
Thread.Sleep(400);
}
}
}
En este ejemplo, dos hilos se ejecutan al mismo tiempo.
El método Join() espera a que el hilo finalice.
Prioridad y Estado de un Hilo
Cada hilo tiene un valor de Priority y una propiedad ThreadState.
La prioridad de un hilo puede influir en la programación del procesador (CPU).
Thread t1 = new Thread(() =>
{
Console.WriteLine("Tarea de baja prioridad.");
});
t1.Priority = ThreadPriority.Lowest;
Thread t2 = new Thread(() =>
{
Console.WriteLine("Tarea de alta prioridad.");
});
t2.Priority = ThreadPriority.Highest;
t1.Start();
t2.Start();
Console.WriteLine($"Estado de t1: {t1.ThreadState}");
Console.WriteLine($"Estado de t2: {t2.ThreadState}");
Nota: El sistema operativo tiene en cuenta la prioridad de los hilos, pero no lo garantiza; depende del planificador de tareas.
Seguridad y Sincronización de Hilos
Cuando varios hilos acceden al mismo recurso (por ejemplo, una variable, lista o archivo), se requiere sincronización. De lo contrario, puede producirse una condición de carrera (race condition).
using System;
using System.Threading;
class Program
{
static int contador = 0;
static object bloqueo = new object();
static void Main()
{
Thread t1 = new Thread(Incrementar);
Thread t2 = new Thread(Incrementar);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine($"Resultado: {contador}");
}
static void Incrementar()
{
for (int i = 0; i < 1000; i++)
{
lock (bloqueo)
{
contador++;
}
}
}
}
La palabra clave lock garantiza que solo un hilo pueda acceder al bloque protegido a la vez,
evitando inconsistencias de datos.
Gestión Ligera de Hilos con ThreadPool
El ThreadPool reduce el costo de crear nuevos hilos para tareas de corta duración. .NET reutiliza automáticamente los hilos disponibles de un grupo compartido.
using System;
using System.Threading;
class Program
{
static void Main()
{
for (int i = 0; i < 5; i++)
{
int id = i;
ThreadPool.QueueUserWorkItem(_ =>
{
Console.WriteLine($"Tarea {id} iniciada (ID del hilo: {Thread.CurrentThread.ManagedThreadId})");
Thread.Sleep(500);
});
}
Console.WriteLine("Todas las tareas enviadas.");
Thread.Sleep(2000); // Esperar a que finalicen las tareas del ThreadPool
}
}
El ThreadPool es ideal para tareas cortas y repetitivas (por ejemplo, solicitudes HTTP o registros de logs).
Diferencias entre Proceso y Hilo
| Propiedad | Proceso | Hilo |
|---|---|---|
| Definición | Instancia de un programa en ejecución | Unidad de ejecución dentro de un proceso |
| Uso de memoria | Tiene su propia memoria | Comparte el mismo espacio de memoria |
| Independencia | Funciona de forma independiente | Depende del proceso |
| Costo de creación | Alto | Bajo |
| Comunicación | Requiere comunicación entre procesos (IPC) | Fácil mediante memoria compartida |
Ejemplo: Descarga de Archivos en Paralelo
En el siguiente ejemplo, cada descarga de archivo se ejecuta en un hilo independiente, lo que permite descargar varios archivos al mismo tiempo.
using System;
using System.Net;
using System.Threading;
using System.IO;
class Program
{
static void Main()
{
string[] archivos = {
"https://example.com/archivo1.jpg",
"https://example.com/archivo2.jpg",
"https://example.com/archivo3.jpg"
};
foreach (var url in archivos)
{
new Thread(() => DescargarArchivo(url)).Start();
}
Console.WriteLine("Descargas iniciadas...");
}
static void DescargarArchivo(string url)
{
string nombre = Path.GetFileName(url);
using var client = new WebClient();
client.DownloadFile(url, nombre);
Console.WriteLine($"{nombre} descargado. (Hilo: {Thread.CurrentThread.ManagedThreadId})");
}
}
Este ejemplo muestra cómo la ejecución paralela puede mejorar significativamente el rendimiento en operaciones de E/S (I/O).
Rendimiento y Consideraciones
- Crear demasiados hilos puede consumir recursos del sistema.
- Bloques
lockdemasiado largos reducen el rendimiento; mantenlos lo más cortos posible. - Para tareas intensivas en CPU, utiliza la
Task Parallel Library (TPL). - Para tareas de E/S, el modelo asíncrono
async/awaites más eficiente. - Los procesos no comparten memoria; utiliza pipes con nombre, archivos o sockets para la comunicación entre procesos.
TL;DR
- Proceso: Instancia de un programa con su propio espacio de memoria.
- Hilo: Flujo de ejecución paralelo dentro de un proceso que comparte memoria.
- ThreadPool: Ideal para muchas tareas cortas.
- lock: Garantiza la sincronización y la coherencia de los datos.
- Demasiados hilos desperdician recursos; usa
Taskoasyncpara mayor eficiencia.
Artículos relacionados
Biblioteca TPL y programación paralela en C#
Aprende Task Parallel Library y programación paralela en C# con Task, Parallel y ejemplos prácticos.
Flujos asíncronos en C# (IAsyncEnumerable)
Aprende flujos asíncronos en C# con IAsyncEnumerable para procesar datos paso a paso con ejemplos prácticos.
Fundamentos de programación asíncrona en C# (async/await)
Aprende async y await en C# para crear aplicaciones fluidas con tareas asíncronas y ejemplos prácticos.