Un programa o proceso contiene al menos un subproceso y puede contener más. En ese caso puede ejecutar varias tareas al mismo tiempo. Estos subprocesos podemos crearlos, ejecutarlos, bloquearlos, suspenderlos, reanudarlos y terminarlos. Si suspendemos un subproceso podremos reanudarlo. Tenemos dos tipos de subprocesos: en primer plano (por defecto) o en segundo plano. Estos últimos terminan automáticamente cuando acaban los procesos en primer plano.
Para esto, .NET incluye el espacio de nombres System.Threading donde se encuentra la clase Thread que contiene lo necesario para poder crear y administrar nuestros propios hilos de ejecución.
Si queremos crear un subproceso debemos usar el constructor proporcionado por la clase Thread(ThreadStart comienzo). ThreadStart es un delegado de C# que contiene el nombre del método que hay que ejecutar para comenzar el subproceso. El tipo devuelto debe ser void y no debe tener ningún argumento. Una vez creado el subproceso lo ejecutamos con el método Start y terminará cuando finalice el método especificado en comienzo.
Con la propiedad IsAlive podemos saber si un proceso aun está en ejecución. Con el método Join() de la clase Thread unimos los subprocesos para que esperen a su finalización y así se finalice el hilo principal (nuestro programa). Veamos el siguiente listado:
using System;
using System.Threading;
class Hilos
{
public int contador;
public Thread hilo;
public Hilos(string nombre)
{
contador = 0;
hilo = new Thread(new ThreadStart(this.ejecucion));
hilo.Name = nombre;
hilo.Start();
}
void ejecucion()
{
Console.WriteLine(«Iniciando subproceso {0}», hilo.Name);
do
{
Thread.Sleep(300);
Console.WriteLine(«Subproceso: {0}. Contador = {1}.», hilo.Name, contador);
contador++;
} while (contador <= 3);
Console.WriteLine(«Terminando subproceso {0}», hilo.Name);
}
}
class Multiproceso1
{
public static void Main()
{
Console.WriteLine(«INICIANDO PROGRAMA PRINCIPAL»);
Hilos h1 = new Hilos(«Primero»);
Hilos h2 = new Hilos(«Segundo»);
Hilos h3 = new Hilos(«Tercero»);
do
{
Console.WriteLine(«*»);
Thread.Sleep(1000);
} while ((h1.hilo.IsAlive) || (h2.hilo.IsAlive) || (h3.hilo.IsAlive));
Console.WriteLine(«FINALIZANDO PROGRAMA PRINCIPAL»);
Console.ReadLine();
}
}
La clase Hilos contiene un contador y un objeto Thread, además del constructor y el método ejecución(). En el constructor inicializamos el contador que se va a usar para finalizar los subprocesos al llegar a un valor y creamos un hilo usando para ello la sintaxis antes explicada.
Como se puede observar, usamos el constructor del thread y el delegado con el nombre del método a ejecutar por el subproceso, en este caso, el método ejecución. Thread.Sleep() duerme la ejecución durante el tiempo expresado entre paréntesis. En el programa principal, creamos los tres subprocesos y esperamos a que finalicen mediante la comprobación realizada mediante IsAlive. De esta forma es sencillo comprobar y sincronizar los subprocesos para esperar a que todos terminen. En este caso usamos un simple OR cortocircuitado de forma que mientras haya alguno de los subprocesos activos no finalice el programa.
Antes hemos comentado la posibilidad de usar Join(). Podemos eliminar el bucle en nuestro programa e incluir estas sentencias:
h1.hilo.Join();
h2.hilo.Join();
h3.hilo.Join();
El método Join() puede aceptar como parámetro el tiempo máximo que debe esperar hasta que termine el subproceso «unido».
Otra propiedad importante es IsBackground. Si queremos especificar o convertir un proceso a segundo plano, no tenemos más que asignar un valor true a la propiedad IsBackground. El hecho de que un proceso esté en primer o segundo plano no tiene por qué afectar a su prioridad salvo que se la asignemos nosotros desde el sistema operativo. Sin embargo, disponemos de una propiedad para controlar las prioridades de los subprocesos (priority). Si tenemos un proceso con una prioridad muy alta pero que se detiene porque espera algún recurso que no está disponible (entrada de datos desde teclado, impresora no preparada, etc.), éste recibirá poco tiempo de cpu. Por eso es importante asignar correctamente las prioridades a los procesos sabiendo qué van a necesitar y que disponibilidad tenemos de los recursos necesarios. Esta propiedad se establece mediante la enumeración ThreadPriority, la cual puede tomar como valores Highest, AboveNormal, Normal, BelowNormal, Lowest.
Cuando creamos un subproceso su prioridad asignada es Normal y la podemos cambiar modificando el valor de la propiedad.
* Basado en un artículo de Jorge Navarrete publicado en la revista Arroba