En muchas ocasiones al diseñar o crear una aplicación en lo último en que se suele pensar es en si debe o no emplear múltiples hilos para su ejecución; ya que cuando se nos enseña un lenguaje de programación lo que se suele hacer es programar todo dentro del mismo programa principal (el famoso Main( )) sin preocuparnos por nada más.
La realidad de las cosas es que con el Hardware moderno se debe explotar al máximo las características que este nos ofrece y para ello son las aplicaciones las que se deben encargar de exprimir esos recursos. Es por ello que he decidido poner un ejemplo que permita esclarecer un poco cómo se deben emplear los Hilos en C# y cómo manejarlos desde una aplicación con interfaz gráfica.
Antes de ello cabe recalcar que siempre que tengamos una necesidad demostrada de diseñar una aplicación que va a estar haciendo más de una cosa al mismo tiempo entonces es ahí en donde debemos pensar en la programación concurrente. Y ¿porqué decidimos utilizar Hilos para ello? pues la idea es simple un Hilo es una especie de proceso muy pequeñito que se va a encargar de ejecutar un mismo trozo de código cada vez que se le diga por tanto consume muy poca memoria RAM y forma parte de un proceso más grande que lo conocemos como programa principal. La idea es que el Hilo le ayude al programa principal a hacer su trabajo; por tanto el programa principal debe delegar algunas actividades para que estas sean realizadas por el Hilo.
Para nuestro ejemplo vamos a diseñar una interfaz gráfica que permita seleccionar la cantidad de hilos que queremos crear y que luego cada hilo se encargue de ir actualizando una etiqueta que servirá como contador y se irá sumando de uno en uno.
La interfaz gráfica será la siguiente:
La opción Archivo → Iniciar lanzará un total de Hilos indicados en el control NumericUpDown; cada hilo se encargará de actualizar la etiqueta de uno en uno.
El código sería el siguiente:
public partial class Form1 : Form
{
private Thread[] hilos = null; //Nuestro arrays de Hilos
private bool[] finalizado = null; //Señales empleadas por los hilos para indicarle al programa principal que ya terminaron
private bool finalizar = false; //Señal empleada por el programa principal para indicarle a los Hilos que terminen
private delegate void setValueDelegate(string valor);
public Form1()
{
InitializeComponent();
}
/*Método empleado por el programa principal para saber si todos los Hilos ya terminaron*/
private bool terminaronTodosLosHilos()
{
for (int i = 0; i < finalizado.Length; i++)
{
if (!finalizado[i])
return false;
}
return true;
}
private void iniciarToolStripMenuItem_Click(object sender, EventArgs e)
{
numericUpDown1.Enabled = false;
hilos = new Thread[Convert.ToInt32(numericUpDown1.Value)];
finalizado = new bool[Convert.ToInt32(numericUpDown1.Value)];
for (int i = 0; i < hilos.Length; i++)
{
finalizado[i] = false;
hilos[i] = new Thread(new ThreadStart(actualizarValor));
hilos[i].Name = Convert.ToString(i);
hilos[i].Start();
}
}
private void actualizarValor()
{
while (true)
{
if (finalizar)
break;
if (Convert.ToInt32(label1.Text) % 2 == 0) //Si el valor es par
{
//lock ((object)label1)
//{
try
{
Monitor.Enter((object)label1);
setValue_Label1(Convert.ToString(Convert.ToInt32(label1.Text) + 1));
System.Console.WriteLine(label1.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Monitor.Exit((object)label1);
}
//}
Thread.Sleep(2000); //Duerme 2 segundos
}
else //Si el valor es impar
{
lock ((object)label1)
{
setValue_Label1(Convert.ToString(Convert.ToInt32(label1.Text) + 1));
System.Console.WriteLine(label1.Text);
}
Thread.Sleep(4000); //Duerme 4 segundos
}
}
finalizado[Convert.ToInt32(Thread.CurrentThread.Name)] = true;
}
private void setValue_Label1(string valor)
{
if (label1.InvokeRequired)
{
setValueDelegate delegado = new setValueDelegate(setValue_Label1);
label1.Invoke(delegado, new object[] { valor });
}
else
label1.Text = Convert.ToString(Convert.ToInt32(label1.Text) + 1);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (hilos == null)
return;
finalizar = true;
while (true)
{
if (!terminaronTodosLosHilos())
Application.DoEvents();
else
break;
}
}
}
Como podemos observar al momento de crear varios Hilos es necesario sincronizarlos para evitar posibles estados inconsistentes o posibles condiciones de carrera. Para ello en C# existe la instrucción Lock o la instrucción Monitor. Otra cosa que es obligatorio hacer es que los Hilos terminen todos correctamente antes de salir de la aplicación; por ello en el evento FormClosing del formulario principal hacemos el uso de las señales respectivas (finalizar y finalizado) para controlar dicha funcionalidad. La señal finalizar es establecido por el programa principal y cuando vale true sirve para indicarle a los Hilos que el programa principal quiere que se terminen. La señal finalizado es establecida por cada Hilo y cuando vale true indica que ese Hilo ya ha finalizado correctamente. Como hay más de un Hilo es necesario crear un array de señales del tipo finalizado para poder controlarlos a todos.
Algo que hay que aclarar es que hay dos formas de lanzar un Hilo, una de ellas es que el Hilo sólo se ejecute una única vez; por tanto se lanza el Hilo, se le dice cuál es el método que él va a ejecutar, el Hilo ejecuta dicho método y cuando termina de ejecutar el método el Hilo termina. Luego hay otra forma de ejecutarlo y es que el Hilo va a estar ejecutando el mismo método de forma reiterada infinitas veces hasta que se le indique que debe terminar. Está última recibe el nombre de Hilo demonio. Para el caso del ejemplo los Hilos que hemos creado son todos del tipo demonio.
Si quieres el código completo lo puedes obtener desde este
Link. Espero te sea de utilidad.