Colaboraciones en el Guille

Dos clases nuevas para trabajar con hilos en el .NET Framework 2.0

Como usar las clases ParameterizedThreadStart y BackgroundWorker

 

Fecha: 20/Sep/2005 (17/Sep/2005)
Autor: Gerardo Contijoch - mazarin9@yahoo.com.ar

 


En este artículos vamos a ver dos clases nuevas para trabajar con hilos en el .NET Framework 2.0. La primera de ellas es ParameterizedThreadStart, una clase (en realidad es un delegado) que nos permite pasarle parametros al método a ejecutar en otro hilo. La segunda clase es BackgroundWorker, la cual nos permitirá ejecutar tareas en otros hilos de una manera muy sencilla sin tener que estar pensando siquiera que estamos trabajando con hilos.

ParameterizedThreadStart

Hasta la llegada del Framework 2.0 cuando necesitabamos iniciar la ejecución de un nuevo hilo creábamos un objeto del tipo ThreadStart el cual era utilizado para instanciar a un nuevo hilo. Por ejemplo:

class Program1
{
    static void Main(string[] args)
    {
        Console.WriteLine("Ejecutando Main....");
        ThreadStart ts = new ThreadStart(UnMetodo);

        Thread t = new Thread(ts);
        Console.WriteLine("Iniciando nuevo hilo...");
        t.Start();
        t.Join();
        Console.WriteLine("Fin de ejecucion de Main.");
        Console.ReadLine();
    }

    static void UnMetodo()
    {
        Console.WriteLine("Estoy ejecutando UnMetodo!!!");
        Thread.Sleep(2000);
        Console.WriteLine("Fin de ejecucion de UnMetodo.");
    }
}

Al ejecutarlo obtendremos:

Ejecutando Main....
Iniciando nuevo hilo...
Estoy ejecutando UnMetodo!!!
Fin de ejecucion de UnMetodo.
Fin de ejecucion de Main.

Aún podemos hacerlo, pero a esta técnica le falta algo: poder pasar parámetros a UnMetodo(). Ahí es donde entra en juego el nuevo delegado ParameterizedThreadStart. Su uso es igual al de ThreadStart. Lo que cambia ahora es la forma de iniciar la ejecución un nuevo hilo. Veamos el ejemplo:

class Program2
{
    static void Main(string[] args)
    {
        Console.WriteLine("Ejecutando Main....");
        ParameterizedThreadStart ts = new ParameterizedThreadStart(MetodoConParametro);

        Thread t = new Thread(ts);
        Console.WriteLine("Iniciando nuevo hilo...");
        t.Start("Saludos desde void Main()!!!");
        t.Join();
        Console.WriteLine("Fin de ejecucion de Main.");
        Console.ReadLine();
    }

    static void MetodoConParametro(object mensaje)
    {
        Console.WriteLine("Estoy ejecutando un método con parametros. Este es el mensaje que llego:");
        Console.WriteLine((string)mensaje);
        Thread.Sleep(2000);
        Console.WriteLine("Fin de ejecucion de un método con parametros.");
    }
}

Como se puede apreciar, el método Start() de la clase Thread ahora posee una sobrecarga, la cual nos permite pasar un objeto como parámetro. El problema de esta técnica es que al tener que pasar un objeto como parámetro, existe la posibilidad de que pasemos un parámetro del tipo incorrecto debido a que perdemos la validación de tipos que realiza el compilador. Si queremos evitar eso podemos recurrir a otro método "compatible" con todas las versiones del Framework.

BackgroundWorker

Para solucionar el problema anterior el Framework 2.0 trae una clase muy util que encapsula bastante código que hasta ahora teníamos que escribir nosotros. Se trata de BackgroundWorker. Su uso es sencillo, y se basa en eventos. Los eventos que expone son los siguientes:

EventoOcurre cuando...
DoWork...se solicita la ejecución de una tarea. Dentro del event handler asociado a este evento debe encontrarse el código a ejecutar en otro hilo. Solo ocurre cuando se ejecuta el método RunWorkerAsync() (de hecho ese método lo unico que hace es lanzar el evento y arreglar las cosas para que se ejecute en otro hilo).
ProgressChanged...se reporta un progreso en el trabajo.
RunWorkerCompleted...se terminó la ejecución de la tarea. La terminación puede deberse a que la ejecución se completó o a que esta fue cancelada.

A traves del método RunWorkerAsync() le indicamos al BackgroundWorker que se debe iniciar la ejecución de una tarea. Imediatamente luego de su ejecución, se produce el evento DoWork, y es ahi donde debemos indicarle que hacer. Durante la ejecución podemos utilizar el método ReportProgress() para lanzar el evento ProgressChanged, el cual podemos utilizar para actualizar un contador, una barra de progreso o simplemente para hacer alguna otra tarea.

Si lo deseamos podemos utlizar el método CancelAsync() para cancelar la ejecución de la tarea, pero cabe aclarar que la simple ejecución del método no cancela la tarea sino que setea en true la propiedad CancellationPending del BackgroundWorker. Nosotros vamos a ser los encargados de monitorear a esta bandera y cancelar el trabajo nosotros mismos.

Una vez terminada la ejecución de la tarea se lanza el evento RunWorkerCompleted. El delegado de evento posee un argumento del tipo RunWorkerCompletedEventArgs que nos ayudará a determinar si la tarea fue cancelada o no y cual fue su resultado.

El código que acompaña a este artículo tiene una pequeña aplicacion que simula realizar una tarea pesada y reporta su estado cada tanto. Obviamente su funcionamiento se basa en el BackgroundWorker.

Como curiosidad voy a mencionar la existencia de una implementación de esta clase en C# para el Framework 1.1. Aca pueden encontrar el código.


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

System.Threading

 


Fichero con el código de ejemplo: qrox_nuevasclaseshilos20.zip - 17 KB


ir al índice principal del Guille