Sunday, January 21, 2007

Quickstart Threads in C#

C# soporta la ejecución paralela de código por medio de multithreading. Un thread o (hilo) es otro camino de ejecución independiente al del proceso principal (o “main” thread) que lo manda a llamar, de esta forma permite la ejecución simultanea con otro hilos.

Un programa de C# empieza con un solo hilo creado automáticamente por el CLR y el sistema operativo. Este hilo se llama “main” thread (o hilo principal) y los otros hilos que son los “worker” threads. El programa se vuelve multhi-threaded (o multi-hilos) al crease threads adicionales.

El CLR asigna a cada thread su propio stack de memoria para mantener las variables locales separadas. Por lo tanto si se hace un método que se ejecuta dos veces en dos threads distintos, entonces existirán dos instancias de las variables en dos stacks. Pero si se desea compartir data entre hilos, se puede hacer si se tiene una referencia a la misma instancia de un objeto. También se puede compartir data entre hilos utilizando campos o variables estáticas (en C# se utiliza la palabra static para definir tales variables).

El problema de compartir data entre hilos es la seguridad entre threads. Sucede los siguiente: puede ser que el thread A empiece a trabajar en las variables o recursos compartidos, cuando thread B todavía esta trabajando en ellos, lo cual afecta el resultado de thread B de una forma no deseada. Similar a lo que pasa cuando dos o mas aplicaciones clientes accesan un registro en una base de datos, definido como concurrencia, solucionado con métodos de control de concurrencia. Para solucionar este problema con los threads se necesita obtener un exclusive lock, de tal forma que el recurso compartido solo puede ser utilizado por un thread a la vez.

Después de esta breve introducción, pueden conocer mas de los threads con ejemplos y todo en esta pagina que considero (ya que me ayudo) muy practica y suficiente para trabajar con threads en C#.

Pero si no desean leer todo el contenido de la página, y desean empezar a trabajar con los threads entonces sigan leyendo. Utilizo los threads en C# para dar una interfase grafica para el usuario que responda durante procesos largos. De esta forma el thread principal Main, podrá estar respondiendo a cualquier requisición del usuario, mientras los hilos trabajan en otras tareas y ventanas.

Para empezar se debe incluir

using System.Threading;

Después de esto creamos el método estático que tendrá todo lo que deseamos correr en el hilo.

private static void metodo()

{

...

}

Si se desea utilizar argumentos, solo se podrá incluir uno. Por lo tanto si se tienen varios argumentos, se pude pasar como argumento un objeto con varias propiedades o un arreglo si todos los argumentos son de un tipo.

private static void metodo(object arg)

{

...

}

Tenemos nuestro metodo ahora a crear el thread. Para crear un thread y ejecutarlo se hace de la siguiente forma:

Thread hilo = new Thread(new ThreadStart(metodo));

Si el metodo tiene un parametro entonces lo declaramos de la siguient eforma

Thread hilo = new Thread(new ParameterizedThreadStart(metodo));

Se declara el hilo con el método que se ejecutara por en el hilo. Para iniciar el thread solo llamamos a:

hilo.Start();

Si deseamos abortar el thread entonces utilizamos Abort(). Para poner a dormir al hilo utilizamos Sleep(n) donde n es el número de segundos que deseamos que el thread duerma.

Dot Net también nos brinda una clase ayudante, BackgroundWorker en el namespace System.ComponentModel para manejar un worker thread. Esta clase nos provee las siguientes bondades:

  • Una bandera “cancel” para mandar una señal al thread de que debe terminar sin utilizar el a veces problemático Abort.
  • Un protocolo estándar para reportar progreso, completación y cancelación. Muy útil para mostrarle a un usuario como va el trabajo.
  • Manejo de excepciones
  • La habilidad de actualizar los controles en las Windows Forms del progreso o finalización del thread. También útil para mejorar la interacción con el usuario.

Algo interesante es que el hecho que BackgroundWorker maneja un pool de threads, donde los recicla en vez de estar creando nuevos threads. Entonces si se desea utilizar esta clase hacemos lo siguiente:

Declaramos el objeto

static BackgroundWorker worker = new BackgroundWorker();

Despues le agregamos los eventos a utilzar. Me parece que los eventos mas utiliados son:

DoWork: donde se programa el trabajo qe efectuara el thread

ProgressChange: donde reportamos el progreso de trabajo a un control como ser una progressbar (barra de progreso).

RunWorkerCompleted: que se encarga de ejecutar lo que queramos cuando termina el thread de ejecutarse.

Estos dos últimos eventos son muy importantes para mejorar la interactividad con el usuario y para comunicarse con el main thread, debido que no es permitido que los hilos se comuniquen entre si, ósea “cross thread comunication” no es permitido.

Así adicionamos los métodos que se ejecutaran en los eventos:

bw.DoWork += new DoWorkEventHandler(bw_DoWork);

bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);

bgWorkerPrintBLs.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

donde los metodos en los parentesis se definen de la siguiente forma:

private void bw_DoWork(object sender, DoWorkEventArgs e)

{

//trabajo del thread

}

void bw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)

{

//intereaccion con el progress bar control

progressBar.Value = e.ProgressPercentage;

}

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

{

//cuando el thread termina puede ser un messagebox o mensaje

MessageBox.Show(“fin”);

}

Finalmente para inicar el este worker thread haemos los siguiente:

bw.RunWorkerAsync();

Conclusion:

Este es un quickstar para threads en C# lo cual es muy probable que se tenga que utilizar en aplicaciones para Windows que no dejen de responder visualmente cuando les toca ejectuar un proceso de varios segundos. Pero esta es solo la punta de iceberg y el ebook (y fuente teorica para este post) que lei para iniciar con los threads en C# es una buen inicio. Alli tambien encontraran sobre sincronizaciones de threds, seguridad, comunicación entre threads y otra informacion teorica y practica muy util.

4 comments:

Anonymous said...

Estimado, para sincronizar el acceso a recursos compartidos debes usar la sentencia "lock". Así:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section
}
para más información revisa este link:

http://msdn2.microsoft.com/en-us/library/c5kehkcz.aspx

SHABAJ said...

Excelente, si tienes mucha razon gracias por el tip. Creo que eso muy importante, obtener exculsion mutua.

saludos

efutch said...

Como el synchronized de Java.

Ariel Arruana said...

El uso de threads en C# es una gran oportunidad para sacar provecho de las arquitecturas multicore (procesadores de múltiples núcleos).

A los interesados en aprender a explotar los procesadores multicore, les recomiendo el libro "C# 2008 and 2005 threaded programming", de reciente lanzamiento por parte de la editorial inglesa Packt Publishing. Se puede desacargar el código para practicar y tiene unos ejemplos muy majos.

Aunque parezca increible, el libro lo ha escrito en inglés un hispanoparlante, el Sr. Hillar Gaston.

http://www.packtpub.com/beginners-guide-for-C-sharp-2008-and-2005-threaded-programming/book

Envían sin costo a todo europa (incluida España). Para américa latina (chateo con colegas hispanoamericanos), la cosa se complica, pues tienen un gasto de envío importante. Pero, brinda soluciones que he estado buscando durante meses leyendo un post tras otro en blogs.

Suerte con la revolución multicore!