30 jul 2008

Inicializar un thread, capturar eventos y usar Invoke (VB.NET)

En este ejemplo haremos una aplicación que al presionar un botón creará un thread aparte que "hará algo", en este caso no hará nada, solo hará tiempo, pero éste thread lanzará un evento al inicio y otro al fin de su ejecución, nosotros capturaremos esos eventos y los mostraremos en pantalla.

Como la interfaz de usuario y el thread obviamente pertenecen a distintos hilos, será necesario utilizar un Delegate con el método Invoke del control que utilizaremos, de lo contrario tendremos una InvalidOperationException por CrossThread

Lo primero que haremos será asignar los manejadores a los eventos que hemos declarado anteriormente, para eso utilizaremos AddHandler y AddressOf.

#Region " Eventos de Thread y sus handlers"
Private Event InicioThread(ByVal Inico As DateTime)
Private Event FinThread(ByVal Fin As DateTime)

Private Trabajando As Boolean = False

Public Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AddHandler InicioThread, AddressOf App_InicioThread
AddHandler FinThread, AddressOf App_FinThread
End Sub

Private Sub App_InicioThread(ByVal Inicio As DateTime)
Trabajando = True
If TextBox1.InvokeRequired Then
TextBox1.Invoke(New MostrarEnTextBox_Delegate(AddressOf MostrarEnTextBox), TextBox1, Inicio.ToString())
Else
MostrarEnTextBox(TextBox1, Inicio.ToString())
End If
If TextBox2.InvokeRequired Then
TextBox2.Invoke(New MostrarEnTextBox_Delegate(AddressOf MostrarEnTextBox), TextBox2, "Todavía haciendo algo...")
Else
MostrarEnTextBox(TextBox2, "Todavía haciendo algo...")
End If
End Sub

Private Sub App_FinThread(ByVal Fin As DateTime)
Trabajando = False
If TextBox2.InvokeRequired Then
TextBox2.Invoke(New MostrarEnTextBox_Delegate(AddressOf MostrarEnTextBox), TextBox2, Fin.ToString())
Else
MostrarEnTextBox(TextBox2, Fin.ToString())
End If
End Sub
#End Region


Como se puede ver en los maejadores de eventos utilizamos un Delegate del método que inserta el texto en las TextBox, eso es porque requiere parámetros, sino lo hubiésemos podido hacer directamente usando AddressOf.
Así que lo siguiente será crear el método que ingresa texto en las TextBox y su correspondiente Delegate.

#Region " Delegate para Invoke "
Private Delegate Sub MostrarEnTextBox_Delegate(ByVal txt As TextBox, ByVal str As String)

Private Sub MostrarEnTextBox(ByVal txt As TextBox, ByVal str As String)
txt.Text = str
End Sub
#End Region


Como dice al principio del post, ésta aplicación tiene un botón que es el que inicia un nuevo hilo, así que agregaremos el botón y en el evento click llamaremos a un método que creará e iniciará el nuevo Thread. Adicionalmente, tenemos un flag para que no permitir la creación de más de un hilo por vez, su valor se cambia con los eventos del Thread.

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not Trabajando Then
InicializarThread()
Else
MessageBox.Show("Hay un 'hilo haciendo' algo aún. Intente nuevamente en unos segundos.", "Hilo trabajando", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub

#Region " Thread "
Private Sub InicializarThread()
Dim t As New Thread(AddressOf HacerAlgo)
t.Name = "Thread_HacerAlgo"
t.IsBackground = True
t.Start()
End Sub

Private Sub HacerAlgo()
RaiseEvent InicioThread(DateTime.Now)
For i As Byte = 0 To 254
Thread.Sleep(30)
Next
RaiseEvent FinThread(DateTime.Now)
End Sub
#End Region


2 comentarios:

  1. Y a todo esto (ya lo capture, lo corrí y todo), ¿cual es la función principal que hace? Aun no capto, ¿me podrías explicar?

    ResponderEliminar
  2. El problema principal a resolver en este ejercicio es que si intentas acceder desde un hilo directamente a la UI, (en este caso el textbox donde escribimos), la aplicación lanzará una Exception.

    Este ejemplo muestra de forma simple cómo evitar ese error utilizando el método Invoke del control y un delegado.


    Imagina que tienes X cantidad de hilos realizando cálculos y quieres ir actualizando la UI mientras los cálculos se están realizando...

    Quedó más claro ahora?

    ResponderEliminar