Академический Документы
Профессиональный Документы
Культура Документы
La clase Thread
Ejecucin asncrona de
Hebras en aplicaciones
Hebras en aplicaciones
Control de la ejecucin
Paralelismo real
Referencias
delegados
Windows
web
de una hebra
La clase Thread
La clase System.Thread representa una hebra del sistema. Las hebras se lanzan cuando se invoca
un delegado sin argumentos que sirve de punto de entrada a la hebra. Al no tener argumentos este
delegado, el estado inicial de la hebra hay que establecerlo previamente en el objeto que aloje el
delegado.
2 de 21
Clculo de PI
Aunque no sea algo especialmente til en el trabajo cotidiano de un
programador, supongamos que, por algn extrao motivo, hemos de calcular
con precisin el valor del nmero PI y no nos vale la constante definida en
System.Math.PI, que slo incluye veinte dgitos de precisin. No obstante, el
proceso que seguiremos para desarrollar una aplicacin multihebra ser el
mismo que el que seguiramos en cualquier otro caso real (p.ej. realizacin de
copias de seguridad mientras nuestra aplicacin sigue funcionando,
implementacin de servidores de aplicaciones, acceso a recursos a travs de la
red sin detener la ejecucin de nuestra aplicacin, etc.). Recordemos que, en
general, la utilizacin de hebras es siempre til cuando hemos de realizar
cualquier tarea que requiera su tiempo...
Comenzamos creando una aplicacin Windows con un formulario principal como
el siguiente:
3 de 21
editDigits
(Align=Right;
Maximum=10000;
4 de 21
5 de 21
6 de 21
// Declaracin
delegate void PiDelegate (int precision);
// Creacin
PiDelegate delegado = new PiDelegate(CalcularPi);
// Uso
delegado((int)editDigits.Value);
7 de 21
Cuando un delegado se usa como si fuese una funcin, en realidad se est llamando al mtodo
sncrono Invoke, que es el que se encarga de llamar a la funcin concreta con la que se instancia el
delegado (CalcularPi en este caso). Los otros dos mtodos del delegado, BeginInvoke y
EndInvoke, son los que permiten invocar al delegado de forma asncrona. De forma que podemos
calcular PI en una hebra independiente de la siguiente forma:
Clculo de PI con delegados
En principio, todo parece ir bien, aunque an nos quedan algunos detalles por pulir...
8 de 21
ShowProgressDelegate showProgress =
new ShowProgressDelegate(ShowProgress);
...
this.Invoke(showProgress, new object[] { pi.ToString(), calculados, precision});
El uso de Invoke nos garantiza que accedemos de forma segura a los controles de nuestra ventana
en una aplicacin multihebra. La hebra principal crea una hebra encargada de realizar una tarea
computacionalmente costosa [la hebra trabajadora] y sta le pasa el control a la hebra principal cada
vez que necesita actuar sobre la interfaz. La siguiente figura ilustra cmo funciona nuestra aplicacin
en tiempo de ejecucin:
9 de 21
Tener que llamar a Invoke cada vez que queremos garantizar el correcto funcionamiento de nuestra
aplicacin multihebra es realmente incmodo y, adems, resulta bastante fcil que se nos olvide
hacerlo en alguna ocasin. Por tanto, no es mala idea que sea la propia funcin a la que llamamos la
que se encargue de asegurar su correcta ejecucin en una aplicacin multihebra:
10 de 21
BeginInvoke es siempre preferible cuando la funcin no devuelve ningn valor ya que evita
que se puedan producir bloqueos.
11 de 21
...
using System.Threading;
public class Payment : System.Web.UI.Page
{
protected Guid ID; // Identificador de la solicitud
private void Page_Load(object sender, System.EventArgs e)
{
if (Page.IsPostBack) {
// 1. Crear un ID para la solicitud
ID = Guid.NewGuid();
// 2. Lanzar la hebra
ThreadStart ts = new ThreadStart(RealizarTarea);
Thread thread = new Thread(ts);
thread.Start();
// 3. Redirigir a la pgina de resultados
Response.Redirect("Result.aspx?ID=" + ID.ToString());
}
}
private void RealizarTarea ()
{
12 de 21
using System;
using System.Collections;
public sealed class Results
{
private static Hashtable results = new Hashtable();
public static object Get(Guid ID)
{
return results[ID];
}
public static void Add (Guid ID, object result)
{
results[ID] = result;
}
public static void Remove(Guid ID)
{
results.Remove(ID);
}
public static bool Contains(Guid ID)
{
return results.Contains(ID);
}
}
13 de 21
La solucin aqu propuesta es extremadamente til en la prctica. Imagine, por ejemplo, una
aplicacin de comercio electrnico que ha de contactar con un banco para comprobar la validez de
una tarjeta de crdito. No resulta demasiado difcil imaginar la impresin del usuario final cuando la
implementacin utiliza hebras y cuando no lo hace.
14 de 21
Para que el usuario mantenga el control absoluto sobre la ejecucin de nuestra aplicacin,
podemos hacer que el botn mediante el cual se inici el clculo de PI sirva tambin para
detenerlo (o crear una ventana de dilogo independiente en la cual se incluya algn botn con la
misma funcionalidad). Si el usuario decide cancelar la operacin en curso, la hebra que controla la
interfaz de usuario debe comunicarle a la otra hebra que detenga su ejecucin y, mientras sta no
se detenga, tenemos que deshabilitar el botn (con el fin de evitar que, por error, se cree una
nueva hebra en el intervalo de tiempo que abarca desde que el usuario pulsa el botn de cancelar
hasta que la hebra realmente se detiene).
Comenzamos definiendo una variable de estado:
enum Estado { Inactivo, Calculando, Cancelando };
Estado estado = Estado.Inactivo;
Implementamos la respuesta de nuestro botn en funcin del estado actual de la
operacin:
private void buttonCalc_Click(object sender, System.EventArgs e)
{
switch (estado) {
case Estado.Inactivo:
// Comenzar el clculo
estado = Estado.Calculando;
buttonCalc.Text = "Cancelar";
PiDelegate delegado = new PiDelegate(CalcularPi);
delegado.BeginInvoke((int)editDigits.Value, null, null);
break;
}
}
case Estado.Calculando:
estado = Estado.Cancelando;
buttonCalc.Enabled = false;
break;
// Cancelar operacin
case Estado.Cancelando:
Debug.Assert(false);
break;
15 de 21
Para que la hebra detenga su ejecucin, podemos hacer que las hebras se comuniquen a
travs de paso de parmetros (por ejemplo, haciendo que la funcin ShowProgress
devuelva un valor a travs de un parmetro adicional, que ser comprobado en cada
iteracin de la hebra para determinar si el usuario ha pedido cancelar la operacin en
curso). Tambin podemos emplear datos compartidos por las hebras, aunque, en ese
caso, deberemos tener especial cuidado con los problemas de sincronizacin y posibles
bloqueos que puedan ocurrir. Si utilizamos esta ltima opcin, podemos escribir lo
siguiente:
delegate void EndProgressDelegate ();
16 de 21
Paralelismo real
Podemos aprovechar el paralelismo real de nuestra mquina para reducir el tiempo de reloj
necesario para realizar un clculo costoso si disponemos de un multiprocesador, de un
microprocesador de doble (o cudruple) ncleo o simplemente tenemos un procesador SMT
(Simultaneous Multithreading, lo que Intel hace comercializa bajo el nombre HyperThreading).
Igual que antes, para que el usuario mantenga el control absoluto sobre la ejecucin de
nuestra aplicacin, hacemos que el botn mediante el cual se inici el clculo de PI sirva
tambin para detenerlo:
// Variable de estado
enum Estado { Inactivo, Calculando, Cancelando };
Estado estado = Estado.Inactivo;
// Cancelar operacin
17 de 21
estado = Estado.Cancelando;
buttonCalc.Enabled = false;
break;
}
}
La actualizacin de la interfaz de usuario la hacemos de forma similar a como la hacamos
antes, si bien hemos simplificado algo la signatura de los mtodos utilizados ya que, ahora, la
precisin deseada de pi ser una variable de instancia de nuestra clase:
18 de 21
int hebras = 0;
19 de 21
private int
precision;
private int[] segments;
private int
current;
private string Pi ()
{
StringBuilder pi = new StringBuilder("3.", precision + 2);
for (int i=0; i<segments.Length; i++) {
string ds = string.Format("{0:D9}", segments[i]);
int nuevos = Math.Min(precision - 9*i, 9);
pi.Append(ds.Substring(0, nuevos));
}
return pi.ToString();
}
Para ir repartiendo el trabajo entre las distintas hebras, definimos un mtodo auxiliar que le
indica a cada hebra qu segmento del nmero pi ha de calcular a continuacin:
20 de 21
21 de 21
Referencias
Shawn Cicoria: Proper Threading in Winforms .NET. CodeProject, May 2003.
Chris Sells: Safe, Simple Multithreading in Windows Forms, Part 1. MSDN, June 28, 2002.
Chris Sells: Safe, Simple Multithreading in Windows Forms, Part 2. MSDN, September 2,
2002.
Chris Sells: Safe, Simple Multithreading in Windows Forms, Part 3. MSDN, January 23,
2003.
David Carmona: Programming the Thread Pool in the .NET Framework. MSDN, June 2002.
10/12/2015 20:01