You are on page 1of 32

Universidad Nacional Autnoma de Nicaragua

Len, Nicaragua

Programacin Visual I
Ingeniera en Telemtica

Tema 8: Programacin concurrente o


multihilo

Pre-requisito: Programacin Orientada a Objetos


Crditos: 4
Semestre: 6to

Ing. Denis Leopoldo Espinoza Hernndez


denisjev@yahoo.es

Contenidos

Introduccin
El hilo principal
Ejemplo de una aplicacin sin hilo
El espacio de nombre System.Threading
La clase Thread
Mtodos de la clase Thread
Propiedades de un hilo
Estados de un hilo
Ejemplo de aplicacin con hilo
Delegados
Acceso a controles empleando Delegados
Componente BackgroundWorker
Mecanismos de sincronizacin
Exclusin mutua
Detener un hilo de forma controlada
Bibliografa

Programacin Visual I
Ingeniera en Telemtica

Introduccin
Las aplicaciones que constan de un nico hilo de ejecucin resultan ms faciles de
implementar y depurar. No pasa lo mismo con las aplicaciones que tiene muchos hilos de
ejecucin las que comparten entre otros recursos la misma zona de memoria.
El diseo correcto de una aplicacin concurrente permite completar una mayor cantidad de
trabajo en el mismo periodo de tiempo.
El objetivo principal de las aplicaciones que implementan hilos, es aumentar el rendimiento
del sistema.
En Windows, cada ventana y cada control pueden responder a un conjunto de eventos
predefinidos. Cuando ocurre uno de estos eventos, Windows lo transforma en un mensaje
que coloca en la cola de mensajes de la aplicacin implicada.

Programacin Visual I
Ingeniera en Telemtica

El hilo principal
Cuando se ejecuta una aplicacin se lanza un hilo principal que se encargar de procesar la
secuencia de eventos que recibe la aplicacin. El hilo principal es el encargado de extraer
los mensajes de la cola y de procesarlos. Evidentemente cada mensaje almacenar la
informacin suficiente para identificar al objeto y ejecutar de forma sincrona el mtodo que
tiene para resoibder a ese evento:

static class Program


{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
Programacin Visual I
Ingeniera en Telemtica

Ejemplo de aplicacin sin hilo


Supongamos que se tienen una aplicacin que posee una interfaz como la siguiente:

Donde se posee una etiqueta llamada etHota la cual se actualiza cada segundo. Un botn
(btCalcular) que inicia la operacin a realizar por la aplicacin, la cual consiste en un ciclo
que realiza la cantidad de iteraciones indicadas en el control NumericUpDown
(numCargaUCP) y una barra de progreso (pbProgreso) que mostrar el avance de la
operacin.

Programacin Visual I
Ingeniera en Telemtica

Ejemplo de aplicacin sin hilo


private void btCalcular_Click(object sender, EventArgs e)
{
btCalcular.Enabled = false;
numCargaUCP.Enabled = false;
pbProgreso.Value = 0;
TareaSecundaria();
}

Mtodo asociado al evento clic

private void TareaSecundaria()


{
int hecho = 0, tpHecho = 0;
while (hecho < numCargaUCP.Value)
{
hecho += 1;
tpHecho = (int)(hecho / numCargaUCP.Value * 100);
if (tpHecho > pbProgreso.Value)
pbProgreso.Value = tpHecho;
}

Mtodo que hace un uso


intenso de CPU

btCalcular.Enabled = true;
numCargaUCP.Enabled = true;
}
}
Programacin Visual I
Ingeniera en Telemtica

Ejemplo de aplicacin sin hilo


Al compilar la aplicacin y probar los resultados. Se observa que la etiqueta que muestra la
hora queda congelada, que la ventana no se mover, si pone encima otra ventana encima o
la minimiza y restaura su posicin la interfaz no se actualiza.
Por qu ocurre esto?
Porque como el hilo principal se encuentra ocupado en el ciclo while no puede atender a
otros eventos que se produzcan (mover la ventana, repintar la venta, etc), quedando estos
en la cola de la aplicacin hasta que finalice la respuesta al evento Clic del botn Calcular,
con lo que la interfaz de la aplicacin se queda congelada.

Programacin Visual I
Ingeniera en Telemtica

El espacio de nombre System.Threading


Un hilo se representa mediante la clase Thread, que se encuentra en el espacio de
nombres System.Threading.
En este espacio de nombres adems de almacenar las clases y delegados que permite la
programacin de multiprocesos. Tambin proporciona las clases de sincronizacin y acceso
a datos:

Mutex
Monitor
Interlocked
AutoResetEvent
Pool (Permite usar un grupo de hilos)
Timer (Clase que ejecuta mtodos de devolucin de llamadas en hilos del grupo de
hilos)

Programacin Visual I
Ingeniera en Telemtica

La clase Thread
Un proceso es un programa en ejecucin. Al crear un proceso el sistema operativo
introduce un hilo (hilo principal) para ejecutar el cdigo. A partir de ese punto pueden
crearse y destruirse otros hilos en el dominio de la aplicacin
Un hilo, tambin llamado subproceso, es un objeto de la clase Thread, cuyo constructor
acepta un nico parmetro un delegado ThreadStart que contiene una referencia al
mtodo que se invocar mediante objeto Thread cuando llame a su mtodo Start del. Si se
llama a Star ms de una vez, se lanzar una excepcin ThreadStateException.
Nuestra aplicacin necesitara poseer un hilo secundario para evitar que se quede
congelada producto de la operacin intensa que realiza:

Hilo
secundario

Programacin Visual I
Ingeniera en Telemtica

Mtodos de la clase Thread


Mtodo

Descripcin

Start

Inicia la ejecucin del hilo

Sleep

Detiene un hilo durante un tiempo determinado

Suspend

Interrumpe un hilo cuando alcanza un punto seguro

Abort

Detiene un hilo cuando alcanza un punto seguro

Resume

Reinicia el hilo suspendido

Join

Deja en espera un hilo hasta que finalice un hilo difente. Si se utiliza con un valor de
tiempo de espera, devuelve true si el hilo finaliza en el tiempo asignado

Programacin Visual I
Ingeniera en Telemtica

10

Propiedades de un hilo
Propiedad

Descripcin

IsAlive

Vale true cuando el hilo esta activo

IsBackGround

Permite obtener o establecer un bool (por defecto vale false), que indica si el
hilo es o debera de ser un hilo en segundo plano. Un hilo en segundo plano
funciona casi igual que un hilo en primer plano, exepto que no permite que
finalice un proceso. SI un proceso en primer plano finaliza, este llama al
mtodo Abort de los hilos en segundo plano activos.

Name

Permite establecer u obtener el nombre del hilo (util para depuracin)

Priority

Permite establecer u obtener valores de prioridad del hilo (Highest,


AboveNormal, Normal, BelowNormal y Lowest)

ThreadState

Describe el estado o estados del hilo

Programacin Visual I
Ingeniera en Telemtica

11

Estados de un hilo
Cuando un hilo es creado, pasa al estado Unstarted. Estado que mantiene hasta que se
llama al mtodo Start, a continuacin se muestran acciones que pueden provocar un
cambio de estado en el hilo.
Accin

Estado resultante

Otro hilo llama a Thread.Start

No cambia

El hilo responde a Thread.Start

Rinning

El hilo llama a Thread.Sleep

WaitSleepJoin

El hilo llama a Monitor.Wait en otro objeto

WaitSleepJoin

El hilo llama a Thread.Join en otro objeto

WaitSleepJoin

Otro hilo llama a Thread.Suspend

SuspendRequested

El hilo responde a Thread.Suspend

Suspended

Otro hilo llama a Thread.Resume

Running

Otro hilo llama a Thread.Interrupt

Running

Otro hilo llama a Thread.Abort

AbortRequested

El hilo responde a Thread.Abort

Aborted

Programacin Visual I
Ingeniera en Telemtica

12

Ejemplo de aplicacin con hilo


El cdigo de la aplicacin empleando la clase Thread nos quedara de la siguiente manera:
//Declaracin del objeto de tipo Thread
private Thread hiloSecundario;
private void btCalcular_Click(object sender, EventArgs e)
{
//Todo el cdigo anterior
//Delegado que hace referencia al mtodo que tiene que se desea ejecutar como hilo
ThreadStart delegadoPS = new ThreadStart(TareaSecundaria);
//Creacin del hilo pasando como parmetro el delegado que apunta al mtodo que se desea
//ejecutar como hilo
hiloSecundario = new Thread(delegadoPS);
//Ejecucin del hilo llamando al mtodo Start
hiloSecundario.Start();
}

Programacin Visual I
Ingeniera en Telemtica

13

Ejemplo de aplicacin con hilo


Al ejecutar el programa con Ctrl+F5 aparentemente todo ha funcionado como esperbamos, la
aplicacin no se congela. Sin embargo en modo depuracin se nos produce el siguiente error:

Esta excepcin ocurre porque los controles de los formularios Windows solo pueden ser accedidos por
el hilo que los cre. No son seguros para la ejecucin de hilos porque si se tuviesen dos o ms hilos
manipulando el estado del control, es posible generar inconsistencias, condiciones de carrera o
interbloqueos. Por eso es importante que el acceso a los controles del formulario se haga de manera
segura para lo cual hay dos formas:
1. Utilizando delegados para habilitar llamadas asincronas para cada propiedad de cada control que
tenga que ser accedido de manera segura desde un hilo.
2. Utilizando un componente BackgroundWorker

Programacin Visual I
Ingeniera en Telemtica

14

Delegados
Un delegado es una clase que puede contener una referencia a un mtodo. nicamente
pueden guardar referencias a los mtodos que coinciden con su prototipo. Por ello
Tenemos dos formas de llamar a un mtodo que tiene que acceder a un control:
1. Sincrona Invoke: el hilo actual queda bloqueado hasta que el y el mtodo
referenciado por el delegado finalice su ejecucin.
2. Asincrona BeginInvoke: Una vez efectuada la llamada al mtodo, se retorna al hilo
actual para continuar su ejecucin.
En nuestro ejemplo, el mtodo TareaSecundaria que se ejecuta como hilo realiza 3 accesos
a controles del formulario principal que debern hacerse empleando delegados:
pbProgreso.Value = tpHecho;
btCalcular.Enabled = true;
numCargaUCP.Enabled = true;

Programacin Visual I
Ingeniera en Telemtica

15

Acceso a controles empleando Delegados


Lo primero que debemos de realizar para el empleo de delegados es hacer que estas
modificaciones que se realizan sobre los controles, se han realizadas desde mtodos y crear
delegados que apunten a esos mtodos.
Tomemos como ejemplo el acceso a pbProgreso. El mtodo debera tener la estructura:
private void SetValue_pbProgreso(int hecho)
{
//Cdigo del mtodo
}

Por lo tanto la declaracin del delegado a emplear debe retornar void y recibir un entero
como parmetro:
private delegate void SetValueDelegate(int prValue);

El delegado se llama SetValueDelegate y el mtodo se llama SetValue_pbProgreso


Programacin Visual I
Ingeniera en Telemtica

16

Acceso a controles empleando Delegados


El cdigo completo del mtodo SetValue_pbProgreso es el siguiente:
private void SetValue_pbProgreso(int hecho)
{
//Si no estamos en el hilo que creo el objeto pbProgreso InvokeRequired devolver true indicando
//que es necesario llamar a Invoke
if (pbProgreso.InvokeRequired)
{
//Se crear un objeto del tipo del delegado antes declarado y se le pasar como parmetro
// el mtodo actual.
SetValueDelegate delegado = new SetValueDelegate(SetValue_pbProgreso);

//Se llamar a Invoke lo cual ejecutar el mtodo apuntado por el delegado pasndole los
//parmetros en un arreglo de object. Esto provocar que se llama nuevamente al mtodo
// actual pero como si fuera el hilo principal con lo que ya no entrar en el if sino en el else
pbProgreso.Invoke(delegado, new object[] { hecho });
}
else
//Se establece el valor a la barra de progreso
pbProgreso.Value = hecho;
}
Programacin Visual I
Ingeniera en Telemtica

17

Acceso a controles empleando Delegados


Ahora en TareaSecundaria cada vez que se desee actualizar la barra de progreso se llamar
a SetValue_pbProgreso:
private void TareaSecundaria()
{
while (hecho < numCargaUCP.Value)
{
if (tpHecho > pbProgreso.Value)
SetValue_pbProgreso(tpHecho);
}
}

Un delegado tambin puede ser un objeto MethodInvoker. Una llamada a un objeto de este
tipo ser ms rpida que una llamada a otro tipo de delegados (como delegate o
EventHandler). La nica restriccin es que este mtodo no permite emplear parmetros.
En nuestro caso ser til para el acceso a btCalcular y numCargaUCP ya que en ambos casos
lo que se desea hacer es poner su propiedad Enabled a true.
Programacin Visual I
Ingeniera en Telemtica

18

Acceso a controles empleando Delegados


Lo primero que debemos hacer es declarar los mtodos que accedern a los controles para
habilitarlos:
//Metodo para acceder a btCalcular desde un MethodInvoker
private void SetValue_btCalcular()
{
btCalcular.Enabled = true;
}
//Metodo para acceder a numCargaUCP desde un MethodInvoker
private void SetValue_numCargaUCP()
{
numCargaUCP.Enabled = true;
}

Declarar un dato de tipo MethodInvoker dentro de la clase:


private MethodInvoker delegado;
Programacin Visual I
Ingeniera en Telemtica

19

Acceso a controles empleando Delegados


Llamar a estos mtodos desde TareaSecundaria con lo que el mtodo ahora tendra la
siguiente estructura:
private void TareaSecundaria()
{
int hecho = 0, tpHecho = 0;
while (hecho < numCargaUCP.Value)
{
hecho += 1;
tpHecho = (int)(hecho / numCargaUCP.Value * 100);
if (tpHecho > pbProgreso.Value)
SetValue_pbProgreso(tpHecho);
}
delegado = new MethodInvoker(SetValue_btCalcular);
btCalcular.Invoke(delegado);
delegado = new MethodInvoker(SetValue_numCargaUCP);
numCargaUCP.Invoke(delegado);
}
Programacin Visual I
Ingeniera en Telemtica

20

Componente BackgroundWorker
BackgroundWorker, es una forma de garantizar que una aplicacin sea sensible a las
acciones del usuario. Este componente se encuentra en el espacio de nombre
System.ComponentModel y esta disponible en panel de componentes de la caja de
herramientas.

Permite ejecutar de forma asincrona y en segundo plano operaciones que consumen


grandes cantidad de tiempo en un hilo diferente al hilo principal. Basta con indicar al
componente cual es el mtodo trabajador y luego llamar al mtodo RunWorkerAsync, este
mtodo puede tomar parmetros que pueden ser pasados al mtodo trabajor.
Cuando el mtodo RunWorkerAsync se ejecuta se genera un evento DoWork. El hilo que
llama (generalmente el hilo principal) continua ejecutandose normalmente y el mtodo
trabajador se ejecutara de forma asincrona.
Cuando el mtodo trabajador termina, el componente BackgroundWorker avisara al hilo
que lo llamo generando el evento RunWorkerCompleted.

Programacin Visual I
Ingeniera en Telemtica

21

Componente BackgroundWorker
Para emplear este componente lo primero que debemos realizar es agregar un control del
tipo BackgroundWorker desde la barra de herramientas. En nuestro caso lo llamaremos
hiloTrabajador
Ahora dentro del evento clic del botn btCalcular llamamos al mtodo RunWorkerAsync del
control hiloTrabajador:
private void btCalcular_Click(object sender, EventArgs e)
{
btCalcular.Enabled = false;
numCargaUCP.Enabled = false;
pbProgreso.Value = 0;
/* Inicia el hilo secundario encapsulado por el objeto BackgroundWorker */
hiloTrabajador.RunWorkerAsync();
}

Esto provocar que se ejecute el mtodo asociado al evento DoWork del hiloTrabajador.

Programacin Visual I
Ingeniera en Telemtica

22

Componente BackgroundWorker
El evento DoWork tiene como parmetro un objeto de la clase DoWorkEventArgs que tiene
dos propiedades: Argument y Result. La primera contendr el valor del parmetro de tipo
object que opcionalmente se puede especificar cuando se invoca al mtodo
RunWorkerAsync, y la segunda se utiliza para asignar el resultado final de la operacin el
cual debera ser recuperado cuando el evento RunWorkerCompleted sea controlado:
/* Metodo que se ejecuta al llamar al mtodo RunWorkerAsync del objeto BackgroundWorker */
private void hiloTrabajador_DoWork(object sender, DoWorkEventArgs e)
{
/* Aunque podriamos colocar aqui el cdigo q tenemos en Tarea secundaria, no lo hacemos
* por si tenemos varios BackgroundWorker que tengan asociado el mismo mtodo al llamar a
* RunWorkerAsync. De esta manera al obtener primero la referencia podramos por ejemplo
* diferenciar entre los diferentes BackgroundWorker antes de ejecutar el cdigo correspondiente
* */
BackgroundWorker hiloTr = (BackgroundWorker)sender;

/* En nuestro caso esta comparacin esta dems por que solo tenemos un BackgroundWorker pero
* se deja para ejemplificar como se llevara a cabo la comprobacin */
if(hiloTr == hiloTrabajador)
TareaSecundaria(hiloTr, e);
}
Programacin Visual I
Ingeniera en Telemtica

23

Componente BackgroundWorker
Cuando llamamos a TareaSecundaria necesitamos pasar la referencia al componente
BackgroundWorker as como los datos del evento (e) para desde el mismo poder facilitar
informacin sobre el progreso de la tarea y su posible cancelacin:
private void TareaSecundaria(BackgroundWorker hiloTr, DoWorkEventArgs e)
{
int hecho = 0, tpHecho = 0;
while (hecho < numCargaUCP.Value)
{
hecho += 1;
tpHecho = (int)(hecho / numCargaUCP.Value * 100);
if (tpHecho > pbProgreso.Value)
{
/* Llamada al mtodo ReportProgress del BackgroundWorker lo que genera el evento ProgressChanged */
hiloTr.ReportProgress(tpHecho);
}
/*Se consulta si se ha solicitado la cancelacin de la tarea por parte del hilo principal*/
if (hiloTr.CancellationPending)
{
/* Aunque no es necesaria colocar Cancel a true para que la cancelacin ocurra esto permite diferenciar desde el
* mtodo RunWorkerCompleted cual fue la razn de la terminacin del hilo */
e.Cancel = true;
break;
}
}
}
24
Ingeniera en Telemtica

Componente BackgroundWorker
Para dar notificacin sobre el progreso de una tarea realizada por un BackgroundWorker
debemos:
1. Establecer la propiedad WorkerReportsProgress a true (desde la ventana de
propiedades).
2. Aadir un controlador al mtodo ProgressChanged el cual es invocado al ejecutarse la
lnea hiloTr.ReportProgress(tpHecho).
private void hiloTrabajador_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pbProgreso.Value = e.ProgressPercentage;
}

El parmetro de tipo ProgressChangedEventArgs define la propiedad ProgressPercentage


que almacena la informacin sobre el progreso, la cual es facilitada por el mtodo
ReportProgress del componente (variable tpHecho). Esta informacin es la que podemos
asignar directamente a la barra de progreso.
Programacin Visual I
Ingeniera en Telemtica

25

Componente BackgroundWorker
El evento RunWorkerCompleted es generado en tres circunstancias diferentes: cuando
finaliza una tarea en segundo plano, cuando es cancelada, o cuando se lanza una
excepcin:
private void hiloTrabajador_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
/* Consulta si se produjo un error */
if (e.Error != null)
MessageBox.Show(e.Error.Message);
/* Consulta si la terminacin se llevo a cabo porque la operacin fue cancelada */
else if (e.Cancelled)
MessageBox.Show("Operacin cancelada");
/* Si no es un error o una cancelacin, la tarea termino correctamente */
else {
btCalcular.Enabled = true;
numCargaUCP.Enabled = true;
}
}

Como se puede observarse, el argumento e tiene las propiedades Error, Cancel, y Result, las
cuales son utilizadas para obtener el estado de la operacin y su resultado final.
Programacin Visual I
Ingeniera en Telemtica

26

Componente BackgroundWorker
Para cancelar la tarea en segundo plano debemos poner la propiedad WorkerSupportsCancellation del
BackgroundWorker a true e invocar el mtodo CancelAsync del componente.
En el siguiente ejemplo realizamos la solicitud de la cancelacin desde el mtodo FormClosing del
formulario:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
/** Indicamos que deseamos cancelar la ejecucin del hilo secundario **/
hiloTrabajador.CancelAsync();
}

De esta cancelacin, la tarea se da cuenta al ejecutar la lnea:


if (hiloTr.CancellationPending) {
e.Cancel = true;
break;
}

Cuando esto se cumple, se asigna a la propiedad Cancel de DoWorkEventArgs el valor true que
posteriormente es reconocido en el mtodo RunWorkerCompleted.
Programacin Visual I
Ingeniera en Telemtica

27

Mecanismos de sincronizacin
Cuando en una aplicacin se ejecuta varios hilos concurrentemente, en muchos casos ser
necesario emplear mecanismos de sincronizacin para que estos coordinen su ejecucin.
La infraestructura de la mquina virtual de .NET, ofrece diversas estrategias para sincronizar
el acceso a objetos y miembros estticos dentro de las cuales se encuentran los objetos de
sincronizacin que permiten realizar una sincronizacin manual.
Objetos de sincronizacin
Sirven para controlar la interaccin entre los hilos y evitar condiciones de carrera y otras
anomalas que se puedan producir. Estos pueden dividirse en 3 categoras: exclusin mutua,
sealizacin e interbloqueo. No obstante algunos mecanismos de sincronizacin pueden
emplearse en ms de una categora.

Programacin Visual I
Ingeniera en Telemtica

28

Exclusin mutua
Garantiza que la seccin crtica ser ejecutada solo por un hilo cada vez y se puede
implementar utilizando cerrojos (lock) o monitores (Monitor).
Lock: Un hilo que acceda a la seccin crtica cuando el cerrojo est echado porque la esta
ejecutando otro hilo, se bloquea hasta que el cerrojo sea liberado.
lock(iteraciones)
{
iteraciones++;
}

La sentencia lock es implementada utilizando los mtodos Enter y Exit de un objeto


monitor y utilizando la sentencia trycatchfinally para asegurar que el cerrojo quede
liberado.

Programacin Visual I
Ingeniera en Telemtica

29

Exclusin mutua
try {
Monitor.Enter(iteraciones);
iteraciones++;
Monitor.Exit(iteraciones);
}
catch(Exception ex) {
Debug.WriteLine(ex.Message);
}
finally {
Monitor.Exit(iteraciones);
}

Donde el mtodo Enter de Monitor marca el principio de una seccin crtica y ningn otro
hilo puede entrar a la seccin crtica, hasta que sea liberado el recurso bloqueado mientras
que el mtodo Exit libera el bloqueo sobre un recurso a la vez que marca el final de una
seccin crtica protegida por el recurso bloqueado.

Programacin Visual I
Ingeniera en Telemtica

30

Detener un hilo de forma controlada


Cuando se cierra una aplicacin y hay hilos secundarios que an no han finalizado, es
probable que surjan problemas si estos no se detienen controladamente. Para realizar el
proceso de cierre correctamente se deben seguir los siguientes pasos:
1. Cuando se vaya ha cerrar la aplicacin, el hilo principal informar a los hilos secundarios
que deben detenerse. Para indicar esta accin, utilizar un controlador de eventos de
espera, por ejemplo controladorPararHiloSecundario.
2. El hilo principal esperar a que los hilos secundarios informen que se han detenido,
pero permitir el procesar de eventos invocando el mtodo Application.DoEvents. Esto
evitar interbloqueos ya que el hilo secundario hace llamadas a Invoke que se procesan
en el hilo principal.
3. Cada hilo secundario verificar en cada iteracin si le ha sido solicitado que se detenga,
en cuyo caso se realizarn las operaciones de limpieza necesarias e informar al hilo
primario de que ha parado utilizando para ello otro controlador de eventos de espera,
por ejemplo controladorHiloSecundarioParado. (Ver ejemplo adjunto)
Programacin Visual I
Ingeniera en Telemtica

31

Bibliografa

Enciclopedia de Microsoft Visual C#, 2da Edicin


Fco. Javier Ceballos Sierra
RA-MA
Captulo 12, pgina 445

Programacin Visual I
Ingeniera en Telemtica

32

You might also like