Esto es una traducción de un artículo de Matthew Arnheiter que se puede encontrar en devx.com (http://www.devx.com/DevX/10MinuteSolution/20365/0/page/4). Explica de manera muy sencilla como adentrarse en el mundo de la programación multihilo con VB.NET. Me pareció muy útil tenerlo traducido para entenderlo mucho mejor... Espero que también le sea útil a cualquera que llegue hasta aquí. En cualquier caso el mérito es de Mathew, a quien NO tengo el gusto de conocer ;).

Ya que VB.Net corre en un Entorno Común de Ejecución (Common Language Runtime) adquiere nuevas capacidades, una de ellas es la habilidad para crear aplicaciones multi-hilo sin restricciones.

Trabajando con Hilos
VB.NET hace sencillo comenzar a trabajar con hilos, Hay algunas peculiaridades que veremos luego, pero lancémonos ya a crear un simple formulario que cree un nuevo hilo para que funcione como un proceso de fondo. La primera cosa que necesitaremos es crear el proceso de fondo en un nuevo hilo. El siguiente código se ejecuta infinitamente – es un bucle infinito.


Private Sub ProcesodeFondo()

Dim i As Integer = 1

Do While True
ListBox1.Items.Add("Iteraciones: " + i)
i += 1
Loop

End Sub


Este código ejecuta el bucle indefinidamente añadiendo ítems en cada iteración a un cuadro de lista (listbox) del formulario. Como observación complementaria, si no estás familiarizado con VB.NET te habrás dado cuenta de algunas cosas más que no no se pueden hacer en VB6.

  • Asignar valores a una variable ene l momento de su declaración.
  • Utilizar el operador +=, en lugar de i=1+1
  • No se utiliza la palabra clave Call.

Una vez que tenemos un proceso de trabajo creado, necesitamos asignar este bloque de código a un nuevo hilo y comenzar su ejecución. Para realizar esto utilizamos el objeto Thread que es parte del espacio de nombres (namespace) System.Threading de las clases del Framework .NET- Cuando instanciamos una nueva clase Thread, en su constructor le pasamos una referencia al bloque de código que queremos ejecutar. El siguiente código crea un nuevo objeto Hilo y le pasa su referencia al ProcesodeFondo:



Dim t As Thread
t = New Thread(AddressOf Me.ProcesodeFondo)
t.Start()


El operador AddressOf crea un objeto delegado del método ProcesodeFondo. En VB.NET un delegado es un puntero a una type-safe función de un objeto. Una vez q se ha instanciado el hilo, arrancamos la ejecución del código llamando al método Start() del Hilo.

Manteniendo todo bajo control

Después del arranque del hilo, tienes algún control sobre su estado utilizando algunos métodos del objeto Thread. Puedes pausar su ejecución llamando al método Thread.Sleep. Este método toma un valor entero que determina cuánto tiempo debe dormir el hilo. Si quisiéramos ralentizar la adición de ítemes al cuadro de lista en el ejemplo anterior, incluiremos una llamada al método Sleep como en este ejemplo de código:



Private Sub ProcesodeFondo()

Dim i As Integer = 1

Do While True
ListBox1.Items.Add("Iteraciones: " + i)
i += 1
Thread.CurrentThread.Sleep(2000)
Loop

End Sub


CurrentThread es una propiedad estática y pública que permite adquirir una referencia al hilo en ejecución.

También podemos hacer dormir al hilo (Sleep) por un periodo de tiempo indefinido llamando a Thread.Sleep (System.Threading.Timeout.Infinite). Para sacar al hilo de este ‘sueño’ podemos llamar al método Thread.Interrupt.

Muy parecidos a Sleep e Interrupt son Suspend y Resume. Suspend nos permite bloquear un hilo hasta que otro hilo llame a Thread.Resume. La diferencia entre Sleep y Suspend es que éste último no pone al hilo en estado de espera inmediatamente. El hilo se suspende hasta que el runtime .NET determina que está en un punto de ejecución seguro para suspenderlo. Sleep pone al hilo en estado de espera inmediatamente.

Por último Thread.Abort detiene la ejecución de un hilo. En nuestro sencillo ejemplo, queremos añadir otro botón en el formulario que nos permita detener el proceso. Para ello lo único que tenemos que hacer es una llamada al método Thread.Abort, así:


Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click

t.Abort()

End Sub


Aquí es donde se puede ve el poder del multithreading. El formulario se nuestra más suelto al usuario porque está corriendo en un Thread.Sleep(System.Threading.Timeout.Infinite).

Pasando datos entre Procedimientos Multihilo

El último ejemplo muestra una situación muy frecuente. La programación multihilo conlleva complicaciones que hay evitar mediante código. Un problema que nos vamos a encontrar es pasar información a y desde el procedimiento al constructor de la clase Thread, Por citar un ejemplo, el procedimiento que arranca un hilo no le puede pasar ningún parámetro ni se le pueden devolver datos a él.

Esto se debe a que el procedimiento que le pasamos al constructor no puede tener parámetros ni un valor de retorno. Para evitar esto, envolvemos el procedimiento en una clase donde los parámetros el método son escritos como campos de la clase.

Un sencillo ejemplo de esto sería un procedimiento que calculase el cuadrado de un número:


Function Cuadrado(ByVal Value As Double) As Double
Return Value * Value
End Function


Para hacer que este procedimiento esté disponible en un Nuevo hilo, tenemos que envolverlo en una clase:


Public Class ClaseCuadrado
Public Valor As Double
Public Cuadrado As Double

Public Sub CalcularCuadrado()
Cuadrado = Valor * Valor
End Sub

End Class


Utilizamos este código para lanzar el procedimiento CalcularCuadrado en un nuevo hilo:

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click

Dim oCuadrado As New ClaseCuadrado()
t = New Thread(AddressOf oCuadrado.CalcularCuadrado)
oCuadrado.Valor = 30
t.Start()

End Sub


Observa que una vez que se arranca el hilo no inspeccionamos el resultado (cuadrado) de la clase, porque no tenemos garantías de que se haya ejecutado una vez que hayamos llamado al método Start del hilo.

Hay varias maneras de retornar valores desde otros hilos. La forma más sencilla es provocar un evento cuando el hilo se ha completado. Veremos también otro método de retornar valores en la siguiente sección cuando hablemos de sincronización. El siguiente código añade las declaraciones del evento a la ClaseCuadrado.


Public Class ClaseCuadrado
Public Valor As Double
Public Cuadrado As Double
Public Event HiloCompleto(ByVal Cuadrado As Double)

Public Sub CalcularCuadrado()
Cuadrado = Valor * Valor
RaiseEvent HiloCompleto(Cuadrado)
End Sub

End Class

Capturar los eventos en el código llamante no cambia mucho desde VB6, se siguen declarando las variables WithEvents y se maneja el evento en un procedimiento. La parte que ha cambiado es que declaramos que un procedimiento maneja el evento utilizando la palabra reservada Handles y no bajo la nomenclatura de Object_Event como en VB6.


Dim WithEvents oCuadrado As ClaseCuadrado

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
oCuadrado = New ClaseCuadrado()
t = New Thread(AddressOf oCuadrado.CalcularCuadrado)
oCuadrado.Valor = 30
t.Start()
End Sub

Sub CuadradoEventHandler(ByVal Cuadrado As Double) _
Handles oCuadrado.HiloCompleto
MsgBox("El cuadrado es " & Cuadrado )
End Sub

Con este método conviene tener en cuenta que es el procedimiento el que maneja el evento, en este caso CuadradoEventHandler, correrá dentro del hilo que provocó el evento. No corre dentro del hilo desde el que se está ejecutando el formulario.

Sincronizando los hilos
VB.NET contiene algunas sentencias que proporcionan sincronización de hilos. En el ejemplo del cuadrado de un número, podríamos querer sincronizar el hilo que hace los calculos para esperar la finalización del cálculo para que podamos recoger el resultado. Otro ejemplo podría ser que tuviéramos un hilo ordenando un array y que quisiéramos esperar a que estuviese ordenado para poder utilizarlo. Para realizar estas sincronizaciones, VBNET proporciona la sentencia SyncLockEnd SynLock y el método Thread.Join.

SynLock obtiene un bloqueo exclusivo a la referencia de un objeto que se le pasa. Obteniendo este bloqueo exclusivo podemos asegurarnos de que múltiples hilos no accederán a datos compartidos o que el código se ejecutará en múltiples hilos. Un objeto que nos conviene utilizar en estos casos es el objeto System.Type asociado a cada clase. El objeto System.Type puede ser recuperado utilizando el método GetType.


Public Sub CalcularCuadrado()
SyncLock GetType(ClaseCuadrado)
Cuadrado = Valor * Valor
End SyncLock
End Sub

Finalmente, el método Thread.Join nos permite esperar un determinado tiempo hasta que el hilo se haya completado. Si el hilo se completa antes del tiempo especificado, Thread.Join devuelve True (Verdadero), de lo contrario devuelve False (Falso). En el ejemplo del cuadrado, si no hubiéramos querido utilizar eventos, podríamos haber llamado al método Thread.Join para saber si había acabado el cálculo. El código hubiera quedado así:


Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click

Dim oCuadrado As New ClaseCuadrado()
t = New Thread(AddressOf oCuadrado.CalcularCuadrado)
oCuadrado.Valor = 30
t.Start()

If t.Join(500) Then
MsgBox(oCuadrado.Cuadrado)
End If

End Sub


Haciéndolo de esta forma conviene tener en cuenta que el procedimiento es quien maneja el evento, en este caso CuadradoEventHandler, correrá dentro del hilo que provocó el evento. No corre dentro del hilo desde el que el se ejecuta el formulario.

9 Response to "Multithreading en aplicaciones VB.Net"

  1. Unknown Said,

    Explicación sencilla, interesante y muy clara. Felicidades por ayudarnos y gracias!. Un saludo

     

  2. Unknown Said,

    Muy buena explicacion, solo me gustaria preguntarte como accedo a los controles del thread del formulario que invoca al subproceso pues megustaria tener una barra de proceso que muestre en estado del proceso.

     

  3. Muy buena explicación. Sencilla y clara. Gracias!

     

  4. Unknown Said,

    Gracias por este articulo, muy bien estructurado y explicado.
    Un gran aporte.

     

  5. Unknown Said,

    Este comentario ha sido eliminado por el autor.

     

  6. Sergio Botta Said,

    Este comentario ha sido eliminado por el autor.

     

  7. Sergio Botta Said,

    Muy buena explicación. Ahora una consulta.. cómo hago para modificar un control de formulario desde un procedimiento que corre en un thread? Muchas gracias.

     

  8. Unknown Said,

    Muy bueno.
    También ejemplos básicos: http://tupagina.comuf.com/

     

  9. Daniel R. Said,

    Que buen artículo. Por mucho tiempo busqué documentación clara como esta y me costó mucho encontrarla.
    Muchas gracias por tu aporte.