Bueno. Ya que no muchos se meten en el area de tutoriales para hacer uno voy a poner algo que me llamo la atencion y que para algunos principiantes en la programacion en Visual Basic puede ser realmente un dolor de cabeza sobre todo por la poca documentacion para principiantes que existe en la red (MSDN no es lo que se podria decir "claro" con respecto a los principiantes).
Actualmente los hilos de ejecucion (Threads) en un entorno de desarrollo se estan volviendo cada vez mas necesarios. En Visual Studio (entiendase como el conjunto de lenguajes que este alberga) estos son bastante simple de crear y controlar. Si bien son bastante faciles de implementar estos tienen algunas limitaciones que los usuarios principiantes pueden obviar y que causarian varios dolores de cabeza.
Normalmente cuando ejecutamos el programa y este tiene funciones que toman demaciado tiempo como puede ser hacer un ping a otro equipo, crear un archivo grande o hacer una descarga desde la web (realmente cualquier funcion que demore mas de 2 segundos ya es mucho), la interface principal del programa se nos queda congelada hasta que la funcion termine y esto puede ser realmente molesto (ademas para lo usuarios finales puede indicar que la aplicacion esta fallando). En estos casos es bueno tener la interfaz y las funciones en hilos de ejecucion aparte para que no se bloqueen entre si. Lo ideal seria que fuesen 2 hilos (El primero para la interfaz y los procesos basicos que son rapidos, y el segundo para procesos largos y lentos).
Primero para comenzar necesitamos alguna version de Visual Studio que se ajuste a nuestras necesidades, en este caso usaremos la version 2008, si bien se dice que puede funcionar con cualquier version yo solo he comprobado su uso en la version 2008 y 2010 por lo que desconosco si versiones antiguas como la 2005 o 2003 puedan servir.
Comenzaremos abriendo el Visual Studio y crearemos una solucion Visual Basic nueva para poder trabajar. Lo ideal seria que ya tuviesemos algun codigo ya escrito para poder modificarlo pero si no existe tendremos que improvisar para hacer las pruebas.
Luego agregaremos algunos componentes al form que tenemos. Agregaremos:
- 1 Boton
- 1 ProgressBar con su propiedad maximum puesta en 100 y su propiedad step en 1
- 1 TextBox con la propiedad Multilinea activada.
(http://ktynza.bay.livefilestore.com/y1pXPLpU_fiZx1Ii-MR7X1bxua3bc12RBtLbrbiSkDLqA7j52KkhQNp5K55aGHTIlRppPR1cA3RWOMxCcDA2-O7FNaOVMijWzql/GUI.PNG)
Luego lo ordenamos en la pantalla de la forma que queramos.
Continuamos agregando alguna funcion que nos tome bastante tiempo. En este caso crearemos una funcion basica que haga ping 1 vez hacia los DNS de google y que luego llamaremos atravez de un buble FOR. Antes eso si, debemos definir alguna variable global para el intercambio de datos.
[spoiler]
La funcion en si solo es esto:
[spoiler]
res = eco.Send(IP)
If res.Status = NetworkInformation.IPStatus.Success Then
Return "OK"
Else
Return "No"
End If
[/spoiler]
Quedandonos todo el codigo asi hasta el momento.
[spoiler]
Imports System.Net ' Importamos las librerias de Red para hacer uso de sus funciones
Public Class Form1
Dim eco As New System.Net.NetworkInformation.Ping
Dim res As System.Net.NetworkInformation.PingReply
Dim ipsx As IPAddress
Dim Resultado As String
Dim i As Integer
Function PingDNS(ByVal IP As IPAddress)
res = eco.Send(IP) ' Recibimos la IP y hacemos Ping
If res.Status = NetworkInformation.IPStatus.Success Then ' Comprobamos si el ping funciono y retornamos la respuesta segun el caso
Return "OK"
Else
Return "No"
End If
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click ' Orden al presionar el boton
ipsx = IPAddress.Parse("8.8.8.8") ' esta es la IP a la cual haremos Ping
For Me.i = 1 To 100 ' Un ciclo FOR para repetir la funcion de Ping varias veces
Resultado = PingDNS(ipsx) ' Pasamos la IP a la funcion PingDNS
TextBox1.Text = TextBox1.Text & vbCrLf & "Respuesta " & i & ": " & Resultado 'Aqui se va mostrando el progreso en el textbox
ProgressBar1.PerformStep() ' hacemos que la barra de progreso de un paso para mostrar avance
Next
End Sub
End Class
[/spoiler]
[/spoiler]
Hasta el momento si ejecutamos el codigo y presionamos el boton veremos como la interface principal se nos queda congelada mientras la funcion este en ejecucion volviendo a la normalidad una vez que esta termina. Hasta aqui todo el programa lleva la ejecucion sobre un solo hilo. Ahora, para crear mas hilos solo debemos agregar el componente BackgroundWorker y modificar un poco el codigo.
El BackGroundWorker lo podemos agregar de 2 formas, agregandolo directamente desde la seleccion de componentes en el IDE (del mismo lugar de donde salio el boton y el textbox), o atravez de codigo que si bien es la forma que me gusta a mi es algo mas complicado para los principiantes ya que hay que definir sus propiedades desde el codigo tambien. En este caso lo vamos a hacer desde el IDE para no hacer tan espeso el tema.
En este caso solo vamos al cuadro de herramientas y elegimos bajo la seccion componentes el BackGroundWorker y lo arrastramos a la ventana principal y cambiamos sus propiedades para que queden igual que en la imagen.
(http://ktynza.bay.livefilestore.com/y1pqrcanlzv6wSlPkErAyZpO2UWYZXwwMsAYC_LghVwojCeC8I6zazW2oliQXOV1ZaowfFrUGX0PW-ym_w9flUGs0FA-I8skmtl/BGW_Comp.PNG)
(http://ktynza.bay.livefilestore.com/y1peD7CMHSxFodz4jF27imqCRfjl25Ko9BAU85PzoWuIwsCFOTkg0bEhBgKtDIX4aOglTfcjgi5to49ksA9BNPDyeTZxrVHTBFa/BGW_New.PNG)
(http://ktynza.bay.livefilestore.com/y1pXPLpU_fiZx0zliYsFbfS7ZRB96ghm69aSrP19xW55Km1AtkWWUp0TQcc_CJfxcjEBWpuGIoH7zYWHIyvKR1uNvF2zC0Wo-Jt/BGW_Prop.PNG)
Ahora viene la parte buena, debemos mover el codigo hacia el evento DoWork del BackgroundWorker (el evento se crea automaticamente haciendo doble clic sobre el BackgroundWorker). Solo moveremos la parte que nos ejecuta nuestra funcion larga, es decir la parte que ejecuta nuestro ping y que se encuentra en el codigo del boton. Hay que tener en cuenta que los BackgroundWorker no pueden acceder a los elementos de la GUI de forma directa por lo que si nos fijamos en el codigo anterior estariamos en problemas ya que estamos interactuando con la ProgressBar y el TextBox. Por suerte para nosotros existe el evento ProgressChanged que nos permitira interactuar con la GUI a medida que reportamos nuevos avances en el trabajo del BackgroundWorker.
Para crear el evento ProgressChanged tan solo debemos seleccionar el BackgoundWorker1 desde el menu superior izquierdo y luego seleccionamos desde la lista superior derecha el nuevo evento ProgressChanged, esto nos creara al final de nuestro codigo un nuevo fragmento en el cual podemos agregar nuestro codigo para interactuar con la GUI.
(http://ktynza.bay.livefilestore.com/y1pqrcanlzv6wS0QT5U0QACMg_qFdRv4cLF5FnWCPSYv93QcAGByHSqpAuE4K-Kc2Wpper6vJTCyvzTOzYVQKClqcZj8sS8V-o_/BGW_ProgChg.PNG)
[spoiler]
El nuevo evento DoWork del BackgroundWorker
[spoiler] Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
' Lo de abajo es el fragmento que gatillaba la funcion Ping
ipsx = IPAddress.Parse("8.8.8.8") ' esta es la IP a la cual haremos Ping
For Me.i = 1 To 100 ' Un ciclo FOR para repetir la funcion de Ping varias veces, 100 en este caso
Resultado = PingDNS(ipsx) ' Pasamos la IP a la funcion PingDNS
BackgroundWorker1.ReportProgress(i) ' Aqui reportamos el progreso que hacemos, en este caso el progreso es el numero de ciclo FOR
Next
End Sub[/spoiler]
y el evento ProgressChanged
[spoiler] Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
' Lo de abajo es el trozo que estaba en el boton e interactuaba con la GUI
ProgressBar1.PerformStep() ' hacemos que la barra de progreso de un paso para mostrar avance
TextBox1.Text = TextBox1.Text & vbCrLf & "Respuesta " & i & ": " & Resultado 'Aqui se va mostrando el progreso en el textbox
End Sub[/spoiler]
El codigo completo hasta ahora.
[spoiler]Imports System.Net ' Importamos las librerias de Red para hacer uso de sus funciones
Public Class Form1
Dim eco As New System.Net.NetworkInformation.Ping
Dim res As System.Net.NetworkInformation.PingReply
Dim ipsx As IPAddress
Dim Resultado As String
Dim i As Integer
Function PingDNS(ByVal IP As IPAddress)
res = eco.Send(IP) ' Recibimos la IP y hacemos Ping
If res.Status = NetworkInformation.IPStatus.Success Then ' Comprobamos si el ping funciono y retornamos la respuesta segun el caso
Return "OK"
Else
Return "No"
End If
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click ' Orden al presionar el boton
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
' Lo de abajo es el fragmento que gatillaba la funcion Ping
ipsx = IPAddress.Parse("8.8.8.8") ' esta es la IP a la cual haremos Ping
For Me.i = 1 To 100 ' Un ciclo FOR para repetir la funcion de Ping varias veces, 100 en este caso
Resultado = PingDNS(ipsx) ' Pasamos la IP a la funcion PingDNS
BackgroundWorker1.ReportProgress(i) ' Aqui reportamos el progreso que hacemos, en este caso el progreso es el numero de ciclo FOR
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
' Lo de abajo es el trozo que estaba en el boton e interactuaba con la GUI
ProgressBar1.PerformStep() ' hacemos que la barra de progreso de un paso para mostrar avance
TextBox1.Text = TextBox1.Text & vbCrLf & "Respuesta " & i & ": " & Resultado 'Aqui se va mostrando el progreso en el textbox
End Sub
End Class
[/spoiler]
[/spoiler]
Hasta ahora si ejecutamos el codigo y presionamos el boton no va a pasar absolutamente nada por que aun no se ha dado la orden para que se active el segundo hilo de ejecucion. Para hacer esto tan solo debemos agregar la instruccion BackgroundWorker1.RunWorkerAsync() bajo el codigo del boton que actualmente no tiene nada ya que movimos el codigo a otras partes.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click ' Orden al presionar el boton
BackgroundWorker1.RunWorkerAsync()
End Sub
Ahora al ejecutar el codigo y presionar el boton la GUI del programa deberia funcionar como siempre e incluso ir mostrando el progreso atravez del textbox y la progressbar.
Algunas consideraciones a tener en cuenta
- Los BackgroundWorkers no pueden acceder a elementos de la GUI por el simple hecho que esta se encuentra en un hilo aparte y tratar de hacerlo nos daria una advertencia de seguridad y nos botaria el programa. De todas formas estos si pueden acceder a variables globales e incluso modificarlas a pesar de que no sea muy recomendado.
- Si un BackgroundWorker no tiene la propiedad WorkerReportProgress en True el evento ProgressChanged no se activara y el worker hara su trabajo sin avisar nada. Esto es util en algunos casos especificos pero no se recomienda mucho.
- Bajo ningun motivo se puede ejecutar la instruccion BackgroundWorker1.RunWorkerAsync() dentro de algun bucle ya que una vez iniciado el hilo este permanecera ocupado hasta terminar o hasta que sea cancelado. En caso de meter la instruccion dentro de un bucle el worker funcionara el primer ciclo pero en el segundo nos dara un error y si no lo tenemos controlado nos hara caer la aplicacion (esto lo pueden comprobar presionando 2 veces el boton en este programa de muestra).
- Buena parte de los eventos necesarios para cualquier programa se crean de forma automatica haciendo doble click sobre el componente. En caso de no crearse siempre se puede hacer desde los menues superiores de la vista de codigo.
Link codigo fuente (http://cid-2d6e43b90ce1b1e2.office.live.com/self.aspx/Tutoriales/BackGroundWorker/bgw%5E_src.rar)