[Tutorial] ¿System.Threading? ¡Cómo y Cuándo! (C#)

Iniciado por [D4N93R], 13 Diciembre 2009, 20:08 PM

0 Miembros y 1 Visitante están viendo este tema.

[D4N93R]

Bueno este es un tuto que escribí hace unos 6 meses, y ahora lo comparto con la comunidad del hacker.net. Si encuentran algún error o algo, me avisan, La idea es ayudarnos y recordar que todos somos humanos.. :)

La programación Multi-Threading es uno de los aspectos peores manejados entre los programadores, por eso antes de comenzar a utilizarlos, debemos saber en qué consiste.



¿Qué es un hilo de ejecución?

Un Hilo de ejecución o Thread es el contexto en donde se está ejecutando una porción de código, es decir, desde que comienza un programa éste fluye a través de un Hilo de ejecución (de ahora en adelante Thread). Antes de que los sistemas operativos soportaran Multi-Threading solamente un Thread era el que llevaba el flujo de la aplicación. Ahora con las nuevas tecnologías y lo avanzados que están los sistemas operativos podemos crear nuevos Threads para desarrollar aplicaciones que las aprovechen y así mejorarlas en varios aspectos como el rendimiento o simplemente mejorar la interfaz de usuario.



Ok ya se que es Multi-Threading, pero ¿Cuándo debo utilizarlo?

Existen varias ocasiones en las que es ideal el uso de un Thread o varios en conjunto para realizar una tarea. Por ejemplo:

Caso 1:

En un formulario tienes una consulta que se tarda aproximadamente 1 minuto, la consulta se ejecuta en el evento Click de un botón llamado button1 (jeje) , el usuario al hacer click y lanzar el evento va a tener que esperar que el proceso termine, mientras ésto ocurre el formulario se deshabilita ya que el Thread que ejecuta el proceso es el mismo que dibuja la ventana, por lo cual aparece el famoso "(No Responde)" en el título.

- Solución: Lanzar el proceso en otro Thread y deshabilitar el botón hasta que termine. Mientras se ejecuta avisar al usuario, ya sea el progreso o simplemente el estado.

Caso 2:

Tienes dos procesos (A y B) que se tienen que ejecutar al mismo tiempo, y no tienen nada que ver uno con otro y normalmente ejecutarías A y luego B para luego continuar el flujo de la aplicación, pero ¿Qué pasaría si A se tarda 30 segundos y B 45 segundos?

- Solución: utilizar dos hilos paralelos para la ejecución y así tomar ventaja de los Procesadores nuevos multi-núcleo.



Existen otros casos, de los que estaremos hablando en próximos post!



Ahora por fin, ¿Cómo lo hago?

Antes de comenzar necesitamos definir un último concepto, llamado Delegado. Un delegado es un tipo de dato especial el cual tiene como función "apuntar" a un método cualquiera. Para que ésto ocurra el delegado debe de estar declarado de forma tal que coincida con la firma del método (Entiéndase firma por los parámetros).

Para todos los ejemplos se usará el siguiente método: void DoProcess(string text).

Cómo uso un delegado:

Primero creamos un delegado que coincida con la firma del método:

delegate void DoProcessDelegate(string text);

luego instanciamos:

DoProcessDelegate delegate = new DoProcessDelegate(DoProcess);

y ejecutamos:

string a = delegate("hola mundo");

Si! así de fácil!

Y ahora bien, existen varias vías para crear un proceso nuevo e iniciarlo:

Utilizando un Delegado

Instanciamos y apuntamos hacia el método:

DoProcessDelegate delegate = new DoProcessDelegate(DoProcess);

Llamamos al método BeginInvoke del delegado para comenzar la ejecución asíncrona donde nos pide los parámetros del delegados más dos, cuales son: AsyncCallback y un object.

El AsyncCallback hace referencia al método que se llama cuando se completa el proceso asíncrono.

El object lo recibe el método que apunta el AsyncCallback.

Ahora tenemos otro método llamado void ProcessEnded(IAsyncResult obj) y pasamos "prueba" como token, aunque debería ser un valor diferente en casos de que se ejecute varias veces la llamada a BeginInvoke En éste caso podemos pasar un Guid: Guid.NewGuid().

delegate.BeginInvoke("Hola Mundo", new AsyncCallback(ProcessEnded), "prueba");

Ahora el  método DoProcess se ejecuta de forma asíncrona y luego el ProcessEnded cuando éste último termine y le llega como parámetro "prueba".

Y ¿Qué pasa si nuestro DoProcess retorna algún valor? pues simplemente en el método ProcessEnded llamamos a delegate.EndInvoke(obj). El obj es el parámetro de ProcessEnded.



Utilizando la clase Thread

Otra forma de ejecutar un método asíncrono es usando la clase Thread. Ésta clase es la base de la ejecución de los Threads. Su funcionamiento es un poco más complicado. Ojo! su funcionamiento más no su llamado, es decir, mediante el uso de la clase Thread tenemos a la mano un conjunto de opciones y clases un poco más avanzadas sobre todo para el tema de la sincronización, de el cual les hablaré en otro post.

Básicamente la clase tiene dos constructores, uno pide un delegado ThreadStart y otro pide un delegado ParametrizedThreadStart, el primero es para apuntar hacia un método sin parámetros y el otro hacia uno con un parametro tipo object.

Ambos funcionan de la misma manera por lo que vamos a utilizar ThreadStart. Ahora tenemos un método nuevo void DeleteAllFiles();

Thread myThread = new Thread(new ThreadStar(DeleteAllFiles));

ahora simplemente para iniciar el thread solo hace falta llamar al método:

myThread.Start();

Éste tiene una sobrecarga de un parametro object para el ParametrizedThreadStart.



La clase ThreadPool

Un pequeño ejemplo del uso de ThreadPool es el siguiente :

El método QueueUserWorkItem tiene un parámetro el cual es un delegado de tipo WaitCallback, que es la referencia al método que se va a ejecutar. También tiene una sobrecarga para pasar un object. 

public void main(string[] args)

{

        ThreadPool.QueueUserWorkItem(new WaitCallback(MyMethod));

         Thread.Sleep(5000);  //El Sleep "duerme" el hilo acual, sino hacemos ésto,

                                          //otra cosa u proceso el hilo del ThreadPool no

                                          //se ejecutará ya que el hilo principal se termina.

}

public void MyMethod(object obj)

{

       //Algún proceso

}



El componente BackgroundWorker

El BackgroundWorker es un componente que podemos arrastrar hasta el formulario, control de usuario o simplemente declararlo dentro de nuestro código. Ejmplo básico de su uso:

static void main(string[] args)

{
     BackgroundWorker worker = new BackgroundWorker();
     worker.DoWork+=new DoWorkEventHandler(worker_DoWork);
     worker.RunWorkerAsync(); //aquí tenemos una sobre carga para pasarle

                                                 //un object al método worker_DoWork

}

static void worker_DoWork(object sender, DoWorkEventArgs e)

{

     // éste código se ejecuta asíncrono

     //en el parámetro DoWorkEventArgs encontramos el object que se envia desde RunWorkerAsync()

     //además de la posiblidad de cancelar en e.Cancel, y de enviar un valor de retorno por e.Result

}



Además de proveernos con funcionamiento asíncrono, el BackgroundWorker nos ofrece maneras para que nuestros métodos "reporten" el progreso del proceso, además de la capacidad de permitir la interrupción de manera fácil de nuestro Thread. El resultado de la operación que se envía a través de e.Result lo podemos encontrar fácilmente suscribiéndonos al evento RunWorkerCompleted del  BackgroundWorker.



Bueno ésto fue todo por ahora! Pronto publicaré más artículos cómo éste y de otros tipos también!..

Gracias por leer! y no se olviden de comentar :)

raul338

:O muy bueno, no lo lei a fondo, pero esta interesante, deben ponerle chincheta a esto ;)

Y tambien deberias ampliarlo, como por ejemplo hacer threads con semaforo (esperar a que uno o todos terminen) o el tipico caso en el que un thread necesita acceder a una variable que pertence a otro thread(un thread padre por ejemplo)

Meta

Muy bueno.

Sleep (5000) es retardo de esa línea de código 5 segundos o 5000 milisegundos.

Sleep también se usa mucho en ASM de microcontroladores PIC.

Saludo.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

[D4N93R]

Gracias por las respuestas,

Cita de: Meta en 13 Diciembre 2009, 21:15 PM
Muy bueno.

Sleep (5000) es retardo de esa línea de código 5 segundos o 5000 milisegundos.

Sleep también se usa mucho en ASM de microcontroladores PIC.

Saludo.

Si es Thread.Sleep() es en milisegundos.!

Cita de: raul338 en 13 Diciembre 2009, 20:21 PM
:O muy bueno, no lo lei a fondo, pero esta interesante, deben ponerle chincheta a esto ;)

Y tambien deberias ampliarlo, como por ejemplo hacer threads con semaforo (esperar a que uno o todos terminen) o el tipico caso en el que un thread necesita acceder a una variable que pertence a otro thread(un thread padre por ejemplo)

Claro, semaforos y mucho más muy pronto.