Colabora
 

Programación Multi-Hebra

[Los Threads]

 

Fecha: 10/Sep/2007 (10-09-07)
Autor: LNS Evolution <Alberto Molero > lobosoft@hotmail

Http://usuarios.lycos.es/xtremecodes

 


Introducción


A lo largo de la vida de Visual BASIC, hemos visto como este sencillo lenguaje de programación se ha ido haciendo mas fuerte con los años, con nuevas caracteristicas y nuevas
funciones, ademas de las mejoras visuales y de ayuda como IntelliSense...
Pues bien, hoy quiero hacer hincapié en una de esas notables mejoras de la cual podemos disfrutar desde la primera versión de visual Basic para .Net Framework.
Dicha característica es la posibilidad de hacer que nuestros programas ejecuten codigo de forma "paralela" al resto.

Osea...si tenemos que realizar una lectura de un archivo que pesa mucho y eso nos va a ocupar mucho tiempo, o cualquier otro proceso que necesitemos realizar y que tarde mucho, eso puede
causar que nuestro programa este "paralizado" durante ese tiempo, hasta que el proceso no finalize.

Contenidos que se explicarán

Bueno antes de nada, empezaremos por la descripción de algunas funciones que usaremos del espacio de nombres ( System.Threading )

New:

Como os podeis imaginar, este es el constructor de la clase, el cual nos pide como único parametro el procedimiento que queremos que ejecute en esta Hebra ( Hilo ).
Ej: Dim SubProceso As New Thread ( Addressof Hola_Mundo() )

Nota: El procedimiento o función a llamar a tràves de una hebra no puede contener parametros.

Abort:

Bueno....pongamonos en el caso de que estamos estudiando para un examen muy importante ( HebraA ), y de golpe y porrazo nos entra hambre, pero no podemos dejar de estudiar!!!
Entonces llamamos a nuestro hermano ( HebraB ), y le decimos que si nos puede ir a comprar una pizza...El va encantado, pero al cabo de media hora, viene papa con la cena inesperada de un restaurante chino, automàticamente decimos...La PIZZA!!!! y le llamamos para decir que no la compre y se venga.... HebraB.Abort()
Pues es lo mismo que en este caso tu puedes anul·lar un subproceso que hayas abierto porque tarda mucho o por cualquier otro motivo.

Start:

Lo usaremos para inicializar la Hebra ( Hilo )
Ej: SubProceso.Start()

Sleep:

No se si os acordais de las API's de VB6...pero havia una declaración que si mal no recuerdo era:
( Private Declare Sub Sleep Lib "Kernel32" ( Byval dwMilliseconds As Long ) ) .
Pues para que engañar-nos... es el mismo procedimiento que en esa declaración de la libreria Kernel32, simplemente que los diseñadores del .Net Framework la han clasificado en el
espacio de nombres System.Threading.Thread, y sirve para crear un retardo en cualquier parte de nuestro procedimiento o función.
Ej:
Imports System.Threading
Private Sub Hola_Con_Retraso()
Thread.Sleep ( 1000 )
Msgbox ("Hola Mundo!!")
End Sub
Es mas facil no?

Join:

Bueno hagamos honor otra vez a otro ejemplito de los mios jejej... (Gràcias a una persona del foro que me explicó bien que era Join() porque hay carencia de información sobre este tema )
Pongamonos en esta situación de nuevo...Estamos estudiando para un examen que es la ostia de chungo ( Difícil ), y papa, tan oportuno como siempre, nos dice que tenemos que ir a comprar
el pan...y nosotros logicamente le decimos que nanai...que hasta que no acabe de estudiar no hago otra tarea!!.
Pues és lo mismo.
La manera más ultilizada es (SubProceso_Que_Hayamos_Iniciado).Join(), esto lo tenemos que poner justo despues del (SubProceso_Que_Hayamos_Iniciado).Start()

ThreadPool.QueueUserWorkItem ( Cola De Espera )

Tambien haremos hincapié en la cola de espera que nos ofrece este espacio de nombres...con esto quiero decir, que con un solo Click en algun boton o con algun otro metodo podemos mandar una lista de procesos y que los vaya el solíto haciendo uno detràs de otro sin que nosotros tengamos que inicializar uno por uno...

Nota: QueueUserWorkItem nos pide dos parametros. Uno es el procedimiento que se pone a la cola, y otro que es el WaitHandle, que como es una clase abstracta, podemos usar un AutoResetEvent.
En el ejemplo ya explicaremos como hacerlo.

Nota:
En este articulo no explicaremos todas las funciones o procedimientos que nos ofrece el espacio de nombres System.Threading ya que es un tema bastante extenso...

Ejemplo 1 ( Simple llamada de un hilo):

A continuación haremos un ejemplo sencillito, que consistira en abrir una nueva hebra ( Hilo o como querais llamarlo ) para realizar una tarea que consistira en escribir el la "Ventana Inmediata"
una variable que ira augmentando hasta llegar a 50 por ejemplo. Como Veremos, si le clickeamos varias veces al boton, nos contará diferentes datos pero en procesos paralelos y totalmente distintos.

Hagamos una tabla de ejemplo para ver que pasaria en nuestra CPU si le picamos 3 veces al boton.

 
1º Ciclo CPU
2º Ciclo CPU
3º Ciclo CPU
4º Ciclo CPU
5º Ciclo CPU
6º Ciclo CPU
Proceso 1
Activado
Esperando
Esperando
Activado
Esperando
Esperando
Proceso 2
Esperando
Activado
Esperando
Esperando
Activado
Esperando
Proceso 3
Esperando
Esperando
Activado
Esperando
Esperando
Activado

Como podemos observar en la tabla, hay tres procesos abiertos pero se van turnando para ejecutarse. Simplemente esto es lo que deduciremos viendo los resultados que se escribirán en la "Ventana Inmediata".

El primero ejemplo constara tan solo de un boton y un procedimiento que lo llamaremos Resultado. Pues bien sin mas preambulos empezemos con el codigo...

Imports System.Threading
Public Class Form1
    Private Sub Resultado()
        For I As Integer = 1 To 50 Step 1
            Debug.Print(I & " De 50") 'Escribimos el mensaje en la pantalla inmediata
            Thread.Sleep(100) 'Entre mensaje y mensaje damos un retardo de 100 Millisegundos
        Next
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        Dim SubProceso As New Thread(AddressOf Resultado)
        SubProceso.Start()
    End Sub
End Class

Sencillo verdad? Entonces sigamos con los siguientes ejemplos

Ejemplo Abort

Bueno en este ejemplito veremos como Abortar un proceso por si este se nos hace muy largo... En nuestro caso haremos un bucle infinito, del cual solo se podra terminar Abortando la hebra que lo lleva a cabo.

Para realizar este ejemplo necesitaremos tan solo dos botones y el procedimiento Resultado(), el cual lo modificaremos para que en vez de un bucle For/Next tenga un Do/Loop.

Imports System.Threading
Public Class Form1
    'Cambiamos el ambito de la variable para que podamos acceder
    ' desde cualquier parte de la Clase Form1
    Dim SubProceso As Thread

    Private Sub Resultado()
        Dim I As Integer
        Do
            I += 1
            Debug.Print(I & " De 50") 'Escribimos el mensaje en la pantalla inmediata
            Thread.Sleep(100) 'Entre mensaje y mensaje damos un retardo de 100 Millisegundos
        Loop
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        SubProceso = New Thread(AddressOf Resultado)
        SubProceso.Start() 'Iniciamos la nueva hebra ( Hilo ) con el procedimiento Resultado()
    End Sub
    Private Sub Button2_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        SubProceso.Abort() 'Abortamos el proceso y asi conseguiremos cerrar el bucle infinito.
    End Sub

d End Class

Ejemplo Join() y Thread.QueueUserWorkItem()

En este ejemplo explicare la manera de hacer colas de espera para realizar procesos en cadena, por ejemplo, tenemos dos procesos uno que abre un archivo y el otro que copia el texto del archivo en el porta-papeles ( Clip Board ). Logicamente no podemos guardar el texto en el porta-papeles si previamente no hemos obtenido el texto del archivo..primero tendriamos que abrirlo y todo eso antes nada no creeis? pues para esto nos puede servir las colas de espera, pues no realizara el siguiente procedimiento o funcion hasta que no acabe el que esta antes.

Para realizar el siguiente ejemplo necesitaremos un boton y dos procedimientos, que los llamaremos Proceso1() y Proceso2()

Imports System.Threading
Public Class Form1
    'Cambiamos el ambito de la variable para que podamos acceder
    ' desde cualquier parte de la Clase Form1()
    Dim SubProceso As Thread

    Private Sub Proceso1()
        For I As Integer = 1 To 50 Step 1
            'Escribimos el mensaje en la pantalla inmediata
            Debug.Print(I & " De 50")
            'Entre mensaje y mensaje damos un retardo de 100 Millisegundos
            Thread.Sleep(100)
        Next I
        'Escribimos el mensaje de finalización en la pantalla inmediata.
        Debug.Print("Proceso1 Finalizado")
    End Sub
    Private Sub Proceso2(ByVal InfoStatus As Object)
        For I As Integer = 1 To 50 Step 1
            'Escribimos el mensaje en la pantalla inmediata
            Debug.Print(I & " De 50")
            'Entre mensaje y mensaje damos un retardo de 100 Millisegundos
            Thread.Sleep(100)
        Next I
        'Escribimos el mensaje de finalización en la pantalla inmediata.
        Debug.Print("Proceso2 Finalizado")
        'Cambiamos el valor de InfoStatus a TRUE para indicar que
        ' ya hemos finalizado el proceso y saltar al siguiente
        CType(InfoStatus, AutoResetEvent).Set()
        'Tambien podriamos usar esto que viene a ser lo mismo que Ctype(Etc...); 
        'DirectCast(InfoStatus, AutoResetEvent).Set()
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        SubProceso = New Thread(AddressOf Proceso1)
        'Declaramos el famoso WaitHandle que comenté anteriormente
        ' para detectar cuando el proceso a finalizado.
        Dim Evento As New AutoResetEvent(False)
        'Iniciamos la nueva hebra ( Hilo ) con el procedimiento Proceso1()
        SubProceso.Start()
        'No dejamos que se inicie otro proceso hasta que el Proceso1 no finalize
        SubProceso.Join()
        'Ponemos el Proceso2 en cola de espera
        ThreadPool.QueueUserWorkItem(AddressOf Proceso2, Evento)
        'No dejamos que se inicie otro proceso hasta que el Proceso2 no finalize
        Evento.WaitOne()
        'Iniciamos el Proceso2 como ejemplo de que pondemos incluso repetirlos... 
        ThreadPool.QueueUserWorkItem(AddressOf Proceso2, Evento)
        'Etc....
    End Sub
End Class

Diferencias Entre VB.Net2003 y VB.Net 2005

Si lo que queremos esque, en vez de escribirlo en la Ventana Inmediata, se escriba todo esto en un ListBox, en la versión de Visual BASIC .Net 2005 nos dara un error en cuyo antecesor no se quejava.
Para solucionarlo simplemente tenemos que poner esto en el evento Load() de nuestro formulario: " Control.CheckForIllegalCrossThreadCalls = False "

Conclusiones Finales

Espero que este pequeño articulo os sirva de ayuda para realizar Sub Tareas sin que este afecte a el buen funcionamiento de nuestra aplicación.

Agradecimientos Por La Ayuda.

Agradezco la ayuda a aibo_alcoi que me hizo una aclaración sobre la función Join(), y a todos los que ayudais desinteresadamente en los foros del guille.
Un Saludo para toda esa gente!!


Espacios de nombres usados en el código de este artículo:

System.Threading


Código de ejemplo (comprimido):

 

Fichero con el código de ejemplo: Threads.zip - (138) KB
(desde la Web del autor)

(MD5 checksum: db9414bc00f3ea519e2d3ecd86046573)

 


Ir al índice principal de el Guille