Ctrl+Alt+Supr, blog de Ollydbg
.MODEL Small .STACK 100h .DATA db msg 'Hello, world!$' .CODE start: mov ah, 09h lea dx, msg ; or mov dx, offset msg int 21h mov ax,4C00h int 21h end start
No hace mucho Btc me comentó algunos problemas que tenía con su B-File Renamer, en concreto con la gestión de hilos que hace su programa para el renombrado masivo de los archivos. Por este motivo me he animado a escribir este pequeño tutorial para mostrar un pequeño ejemplo práctico de como implementar una 'solución' que emplea el uso de hilos (threading en inglés).
Una de las principales ventajas de las aplicaciones que emplean hilos es que pueden hacer 'más de una cosa' al mismo tiempo. En la realidad prácticamente todas las aplicaciones ya usan internamente más de un hilo, lo que ocurre es que esto se realiza de forma transparente para nosotros.
En nuestro caso queremos que un determinado método, función, clase o lo que sea se ejecute en otro hilo. Normalmente estos métodos, funciones, clases o lo que sea que queremos ejecutar dentro de un hilo consumen bastante tiempo.
Pongamos el caso del B-File Renamer. Renombrar todos los archivos de una determinada carpeta puede llevar 1 minuto o más, dependiendo lógicamente de la cantidad de archivos a renombrar.
Lo que no debería hacer la aplicación (B-File Renamer en este caso) es 'quedarse colgada' mientras renombra 2000 ficheros.
En este tipo de escenarios se puede implementar un BackgroundWorker, que es lo que veremos a continuación.
Para ello he simulado una aplicación que calcula la suma de todos los números comprendidos entre 1 y 1000
A esta suma se le ha añadido un retardo (Sleep) para 'simular' la carga de trabajo del método en cuestión.
La aplicación básicamente es esta:
Se calculará la suma sin usar hilos y usando un BackgroundWorker
Además se dispone de una barra de progreso que indica cuanto falta para finalizar todo el proceso y un botón de 'stop' que detiene el proceso.
Si se detiene el proceso se indica en la etiqueta correspondiente con el texto "Cancelado"
Si ejecutáis el programa, aparentemente tanto la opción ejecutar sin 'Hilos' como la opción usar 'BackgroundWorker' funcionan igual: muestran el resultado, la barra de progreso se va actualizando, el botón 'stop' funciona... sin embargo esto es solo apariencia. :)
Si ejecutáis el ejemplo sin usar hilos, intentar mover la ventana mientras se está ejecutando. Oh sorpresa! Observaréis que tanto el resultado como la barra de progreso 'se han parado'.
Cuando dejéis de mover la ventana observaréis que tanto el resultado como la barra de progreso se vuelven a actualizar.
Pero hay más, si ahora pulsáis sobre el menú Archivo, al seleccionar cualquier elemento del menú se muestra un MessageBox. Al mostrarse el MessageBox se puede observar que tanto el resultado como la barra de progreso 'se han parado'
Nada de esto ocurre cuando se usa un BackgroundWorker. Podéis mover la ventana de la aplicación o pulsar en cualquier item del menú y veréis que tanto el resultado como la barra de progreso se siguen 'actualizando'
Del BackgroundWorker nos serán útiles 3 eventos:
DoWork
El evento DoWork se dispara cuando se llama al método RunWorkerAsync
En nuestro caso tendremos algo como esto:
Private Sub BackgroundWorker1_DoWork()
e.Result = DoAlgo()
End Sub
DoAlgo() es la función que calcula la suma de los 1000 primeros números. Esta función devuelve el resultado en un dato de tipo Integer que se asigna a e.Result que es de tipo genérico (Object)
Cuando DoAlgo() haya calculado la suma de los 1000 primeros números, guardará el resultado de dicha suma en e.Result y se disparará inmediatamente el envento RunWorkerCompleted()
Para evitar que se dispare el evento DoWork antes de que el BackgroundWorker haya finalizado la tarea se puede llamar a la función IsBusy:
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
ProgessChanged
Este evento se dispara cuando se llama al método ReportProgress
Dentro de nuestra función DoAlgo() vamos sumando los números. Al tratarse de un simple bucle For se puede computar fácilmente el porcentaje de números que llevamos sumados:
valorPorcentaje = Convert.ToInt32((i / Total) * 100)
BackgroundWorker1.ReportProgress(valorPorcentaje)
Dentro del evento ProgressChanged tenemos:
Private Sub BackgroundWorker1_ProgressChanged()
Me.ProgressBar1.Value = e.ProgressPercentage
End Sub
Como se puede ver, lo único que se hace es actualizar el valor un control ProgresBar
Aquí cabe destacar que en este caso no es necesario utilizar ningún delegado para actualizar el contenido del control Progressbar.
Si intentáis cambiar el texto, la visibilidad, enable/disable, etc de un control (botón, etiqueta, caja de texto, listview, etc) desde dentro de un hilo distinto al hilo principal, deberéis usar delegados.
En nuestro ejemplo se muestra el uso de un delegado en la función MostrarResultado() que actualiza el texto de la etiqueta LblStatus desde dentro de DoAlgo() . DoAlgo() se está ejecutando en un hilo distintoal hilo principal del programa.
Otra opción es usar por ejemplo en el Form_Load() la siguiente instrucción: (MSDN)
Control.CheckForIllegalCrossThreadCalls = False
Con eso podríamos usar LblStatus.Text = retSuma.ToString() desde dentro de DoAlgo() sin que se produjese ninguna excepción.
RunWorkerCompleted
Este evento se dispara cuando el método, función, etc que se ha iniciado en DoWork ha finalizado. También se dispara este evento cuando se cancela el proceso.
Para cancelar el proceso se llama al método CancelAsync
En nuestro caso tenemos un botón "stop" que hace lo siguiente:
BackgroundWorker1.CancelAsync()
Luego, dentro de nuestro DoAlgo() tenemos algo como esto:
For xxxx
retSuma = realizar suma
If BackgroundWorker1.CancellationPending Then
retSuma = Nothing
Exit For
End If
Next
Return retSuma
Esto provoca que salga del bucle For y que el resultado que se guarda en e.Result sea Nothing.
Finalmente en el evento RunWorkerCompleted hacemos lo siguiente:
Private Sub BackgroundWorker1_RunWorkerCompleted()
If e.Result <> Nothing Then
LblStatus.Text = e.Result
Else
LblStatus.Text = "Cancelado"
End If
End Sub
Pues poco más hay que añadir. Cómo habréis visto el uso de un BackgroundWorker es bastante sencillo.
En .NET Framework 4.0 se ha introducido un nuevo concepto para el manejo de 'hilos'. Es lo que se conoce como TPL: Task Parallel Library (MSDN)
La TPL es un concepto interesantísimo al que seguramente dedicaremos alguna entrada.
Saludos.
mov eax,ollydbg; Int 13h
Descargar código fuente del .NET Tutorial 51
(23 KB. Visual Studio 2008)
.MODEL Small .STACK 100h .DATA db msg 'Hello, world!$' .CODE start: mov ah, 09h lea dx, msg ; or mov dx, offset msg int 21h mov ax,4C00h int 21h end start
Nuevos enlaces descargas Tutoriales
1 al 24, MouseOdometerNET, PingScanner y
OllySecrets (1 y 2) (Actualización 05/02/2011)

1 Comentario:
Disculpen, ¿saben donde
26 de Agosto de 2011 • 19:53 — BtcDisculpen, ¿saben donde esta por aqui el departamento de quejas? Tengo que poner 10 estrellas y en mi pantalla solo veo 5!!
Impresionante entrada, lo unico que me tiene un poco liado, es el objeto "e", ya que cambia según el invocador. Por ejemplo, si hablamos de un keypress en un textbox, "e" transporta el caracter o la tecla que se pulsa. Tengo que echarle un buen vistazo a eso.
Saludos!