Cargando...

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

PropiedadProcesoHilo
DefiniciónInstancia de un programa en ejecuciónUnidad de ejecución dentro de un proceso
Uso de memoriaTiene su propia memoriaComparte el mismo espacio de memoria
IndependenciaFunciona de forma independienteDepende del proceso
Costo de creaciónAltoBajo
ComunicaciónRequiere 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


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 Task o async para mayor eficiencia.

Artículos relacionados