Jul 22 2008

Manejo de hilos en C# (parte 1) + delegates + invoke {

Tag: C#

En el artículo que explica la implementación del patrón Singleton en C# utilizamos un método que inicializa 42 hilos los cuales modifican un atributo del objeto Singleton. Además de eso, cuando se lanza el evento que “avisa” de la modificación del atributo, se muestra el valor en una label del form, por lo cual deberemos usar delegados para evitar un problema de CrossThreadException.

Primero veamos el código que crea los threads:

private void button2_Click(object sender, EventArgs e)
        {
            System.Threading.ThreadStart ts = new System.Threading.ThreadStart(sumarRandomico);
 
            Int16 i;
            for (i = 0; i <= 42; i++)
            { 
                System.Threading.Thread t = new System.Threading.Thread(ts);
                t.IsBackground = true;
                t.Name = String.Format("Thread_{0}", i.ToString());
                t.Start();
                System.Diagnostics.Trace.WriteLine(String.Format("Thread {0} >>> Contador: {1} <<<", t.Name, objetoSingleton.Instancia.Contador.ToString()));
            }
        }

En la primera linea creamos el ThreadStart, que es el que indica el método que se ejecutará en el thread, luego dentro del bucle for creamos el objeto thread, seteamos que se ejecute en background, le seteamos un nombre y luego iniciamos la ejecución del hilo. Hasta ahí no hay misterio, simplemente instanciamos e iniciamos varios hilos, en caso de que el método que se ejecutará en el nuevo hilo requiera parámetros, utilizaremos un ParameterizedThreadStart que veremos en la segunda parte de este artículo.

Ahora veamos cómo mostrar en pantalla el valor evitando las CrossThreadException, las mismas suceden cuando se intenta acceder a un objeto de un hilo, (la interfaz en este caso), desde otro hilo.
Cuando se intenta acceder a un control desde otro hilo, la propiedad InvokeRequired toma el valor true, por lo que consultando el valor de esta propiedad sabremos si podemos acceder directamente al control o no.

void Instancia_CambioContador(long nuevoValor)
        {
            if (!this.label2.InvokeRequired)
            {
                CambiarValorLabel(nuevoValor.ToString());
            }
            else
            {
                CambiarValorDelegate del = new CambiarValorDelegate(CambiarValorLabel);
                this.label2.Invoke(del,nuevoValor.ToString());
            }
        }
 
        private delegate void CambiarValorDelegate(String nuevoValor);
        private void CambiarValorLabel(String nuevoValor)
        {
            this.label2.Text = nuevoValor;
        }

Como vemos, si InvokeRequired es false, llamamos directamente al método que modifica el Text de nuestra label, de lo contrario necesitaremos crear un delegate para ese método y utilizarlo con el método Invoke del control.
Un delegate es, a grandes rasgos, un puntero, lo cual en este caso evita la CrossThreadException porque accede directamente a la dirección de memoria del método que llamaremos. Para utilizar un delegate, tenemos que definirlo primero y éste debe tener la misma firma que el método que llamará.

Al momento de utilizarlo, instanciamos nuestro delegate y utilizamos el método Invoke de nuestra label:

CambiarValorDelegate del = new CambiarValorDelegate(CambiarValorLabel);
this.label2.Invoke(del,nuevoValor.ToString());

}


Página 1 de 11