Home :: Otros :: Productividad :: Cómo crear una aplicación multihilos (multithread). AutoResetEvent vs. BackgroundWorker en Visual Studio 2008, C#
 
threadingcsharp

Cómo crear una aplicación multihilos (multithread). AutoResetEvent vs. BackgroundWorker en Visual Studio 2008, C#

nov 10, 2010 en Productividad por Victor Parasi

Los métodos mas conocidos para realizar tareas en segundo plano son la clase AutoResetEvent y el componente BackgroundWorker, pero ¿qué usar en cada caso? Sencillo, si lo que requieres es poner varias tareas en segundo plano pero es necesario que tu aplicación espere a que todas las tareas hayan terminado para continuar con el flujo entonces la opción es AutoresetEvent; por otro lado, si lo que se busca es poner procesos pesados en segundo plano y no es necesario esperar a que todos terminen al mismo tiempo entonces implementa BackgroundWorker, este último con la ventaja sobre AutoresetEvent, que puede tener acceso a la interfaz de usuario al termino de cada tarea.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Antes de empezar, queremos agradecer a Yared Castro, por colaborar en la realización de este interesante post. 

Sin importar el lenguaje que utilices, lo importantes es que debes siempre saber que: Al ejecutar una aplicación, el sistema operativo (sea Winodws o Linux) inicia un proceso para la aplicación y le asigna una serie de recursos para poder ejecutarse.

Es sobre este proceso (o llamado también hilo principal de ejecución), es sobre el cual aparece la primera pantalla de nuestra aplicación y se "cuelgan" las demás pantallas para ejecutarse.

Indudablemente que, si tenemos una opción que trabaja con máquinas o servidores ubicados remotamente, la invocación, ejecución y visualización de la información puede demorar más de lo debido. Una forma para aminorar esto o al menos hacer que nuestra aplicación siga ejecutándose de forma normal, mientras se procesa una operación es usar hilos (thread en inglés)

Definamos a un thread, como una unidad (bloque de código) que es ejecutado para realizar una operación determinada que: demora más de lo planeado o se quiere hacer de forma paralela. En ambos caso lo que hace el Sistema Operativo es lanzar un subproceso del proceso principal, de forma que desde el hilo principal de ejecución se desprenden las demás invocaciones. Algunos ejemplos del uso de thread, tenemos:

  • Descargas de imágenes
  • Invocaciones del servicio Web
  • Descargas y cargas de archivos (incluso para las aplicaciones punto a punto)
  • Cálculos locales complejos
  • Transacciones de base de datos
  • Acceso del disco local, dada su baja velocidad relativa al acceso a memoria

Aunque hay varias formas de implementar thread en nuestras aplicaciones, este artículo va a enseñar sólo dos de ellas: AutoResetEvent y usando el componente BackgroundWorker

AutoResetEvent

Notifica que se ha producido un evento a un subproceso en espera y permite que los subprocesos se comuniquen unos con otros mediante señalamientos. Un subproceso espera una señal mediante una llamada a WaitOne en el método que se esta procesando en el AutoResetEvent. Si AutoResetEvent se encuentra en el estado no señalado, el subproceso se bloquea en espera de que el subproceso que controla el recurso en ese momento indique que dicho recurso está disponible mediante una llamada a Set.

A diferencia de BackgroundWorker, no contempla eventos para reportar progreso de ejecución de los hilos (threads) y se debe considerar que los “try catch” van en cada método para el manejo de excepciones que es una diferencia mas con BackgroundWorker.

clip_image002[6]

Figura 1. Sincronización de threads con AutoResetEvent

Es posible que cada hilo se vaya enterando de lo que hace uno anterior mediante los señalamientos que comento en el primer párrafo, sin embargo, para efectos demostrativos únicamente de este artículo se esta tomando en cuenta que se desea sincronizar las tareas y estar al tanto del momento en todas las que se pongan en el ThreadPool hayan terminado.

 

//Incluir en nuestra clase:
using System.Threading;
.
.
.

//Se instancia la clase, en este caso son tres hilos los que se ilustran
autoEventos = new AutoResetEvent[]
{
  new AutoResetEvent(false),
  new AutoResetEvent(false),
  new AutoResetEvent(false)
};
.
.
.

        //Se pone la tarea pesada por hilo dentro de cada metodo
        void LanzaHilo1(object stateInfo)
        {
            for (int i = 0; i < 300; i++)
            {
                System.Threading.Thread.Sleep(50); //simulando trabajo
            }
            _hilo1Completado = true;
            //Se libera recurso
            autoEventos[0].Set();
        }

        void LanzaHilo2(object stateInfo)
        {
            for (int a = 0; a < 100; a++)
            {
                System.Threading.Thread.Sleep(50); //simulando trabajo
            }
            _hilo2Completado = true;
            //Se libera recurso
            autoEventos[1].Set();
        }
.
.
.
        //Se lanzan los hilos
        string LanzaHilosJuntos()
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(LanzaHilo1));
            ThreadPool.QueueUserWorkItem(new WaitCallback(LanzaHilo2));
            ThreadPool.QueueUserWorkItem(new WaitCallback(LanzaHilo3));

            WaitHandle.WaitAll(autoEventos);

            label1.ForeColor = Color.Blue;
            label2.ForeColor = Color.Blue;
            label3.ForeColor = Color.Blue;

            return "Hilos terminados";
        }

Nota: En caso de implementar el código y que se presente un error del tipo “WaitAll For Multiple Handles on a STA Thread Is Not Supported”, existen dos posibles soluciones, la primera es en el program.cs de la aplicación cambiar el atributo [STAThread] por [MTAThread] y con esto indicamos que nuestra aplicación es multiprocesos; la otra solución es usar ManualResetEvent en vez de AutoResetEvent.

BackgroundWorker

El componente BackgroundWorker proporciona la capacidad de ejecutar operaciones que llevan mucho tiempo de forma asincrónica ("en segundo plano"), en un subproceso diferente del subproceso principal de la interfaz de usuario de la aplicación. Para utilizar BackgroundWorker, simplemente hay que indicarle el método de trabajo cuya ejecución en segundo plano lleva mucho tiempo y, a continuación, llamar al método RunWorkerAsync . RunWorkerAsync toma un parámetro object opcional, que se utiliza para pasar argumentos al método de trabajo. Este método de trabajo es el evento DoWork que la clase BackgroundWorker expone, es en donde se pone la carga de trabajo que tardará tanto tiempo. Cuando finaliza el método, BackgroundWorker avisa al subproceso que hace la llamada desencadenando el evento RunWorkerComplete, que puede contener los resultados de la operación y que es en donde podemos tener acceso a la interfaz de la aplicación.

Usando BackgroundWorker se recomienda el uso de excepciones sobre los parámetros que devuelve DoWork en los argumentos que se pasan a RunWorkerComplete mediante “e.Error” en vez de usar un “try catch” por cada llamada.

clip_image002[4]

Figura 2. Realizando tareas en segundo plano con BackgroundWorker

Para conocer las mejores prácticas en el subprocesamiento administrado te recomiendo que visites: http://msdn.microsoft.com/es-es/library/1c9txz50(v=VS.80).aspx

//Se inicializa el componente para cada hilo
BackgroundWorker _hilo1 = new BackgroundWorker();
_hilo1.DoWork += new DoWorkEventHandler(_hilo1_DoWork);
_hilo1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_hilo1_RunWorkerCompleted);
_hilo1.ProgressChanged += new ProgressChangedEventHandler(_hilo1_ProgressChanged);

//Se manda llamar el metodo para ejecutar la tarea pesada
_hilo1.RunWorkerAsync();

BackgroundWorker _hilo2 = new BackgroundWorker();
_hilo2.DoWork += new DoWorkEventHandler(_hilo2_DoWork);
_hilo2.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_hilo2_RunWorkerCompleted);
_hilo2.ProgressChanged += new ProgressChangedEventHandler(_hilo2_ProgressChanged);

_hilo2.RunWorkerAsync();
.
.
.
        //Empieza el trabajo pesado
        void _hilo2_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(50); //simulando trabajo
                bw.WorkerReportsProgress = true;
                bw.ReportProgress(i);
            }
            e.Result = true;
        }

        //Se completa el trabajo pesado y ya se tiene acceso a la interfaz de usuario
        void _hilo2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lblProgresoTotal.Text = "Hilo 2 completado";
            hilo2Completado = Convert.ToBoolean(e.Result);
            txtHilo2.Enabled = false;
            label2.ForeColor = Color.Blue;
            lblProgresoHilo2.ForeColor = Color.Blue;

            if (hilo1Completado && hilo2Completado && hilo3Completado)
                btnComienzaHilos.Enabled = true;
        }

        //Progreso del trabajo asignado en DoWork
        void _hilo2_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lblProgresoHilo2.Text = e.ProgressPercentage.ToString() + "%";
            txtHilo2.Text = txtHilo2.Text + e.ProgressPercentage.ToString() + Environment.NewLine;
        }

Demo

En el código que se adjunta, se muestra de forma sencilla una manera de usar AutoResetEvent y BackgroundWorker así como la principal diferencia entre ambos.

clip_image002[8]

Figura 3. Usando BackgroundWorker (descargar)

clip_image004

Figura 4. Usando AutoresetEvent(descargar)

Conclusión

Cuando se usa BackgroundWorker se tiene la flexibilidad de tener acceso a la interfaz de usuario desde RunWorkerCompleted una vez que se van completando las tareas en segundo plano.

AutoResetEvent es de gran ayuda cuando es necesario realizar varios procesos al mismo tiempo con la funcionalidad de sincronización de tareas para esperar el término de cada hilo y poder hacer uso del resultado de cada uno en un proceso posterior.

Ambos casos (AutoResetEvent y BackgroundWorker) ven en aumento su performance con las maquinas de los últimos años que contienen núcleos con más de un procesador, ya que realmente pueden ejecutar varios procesos paralelos.

Fuentes AutoResetEvent

http://msdn.microsoft.com/es-es/library/system.threading.autoresetevent(v=VS.90).aspx

http://blogs.msdn.com/b/johnlee/archive/2007/07/10/waithandle-waitall-for-multiple-handles-on-a-sta-thread-is-not-supported.aspx

Fuentes BackgroundWorker

http://msdn.microsoft.com/es-es/library/ms233672(v=VS.80).aspx

http://msdn.microsoft.com/es-es/library/8xs8549b(VS.80).aspx

http://dotneat.iloire.com/2010/03/06/ejemplo-backgroundworker-en-csharp/


Autor: Victor Parasi

Siempre es difícil escribir sobre uno mismo, qué contar, o por donde empezar, suele ser todo un dilema al momento de presentarse. Aquí vamos. Les diré que soy peruano, Ingeniero por vocación, dedicado a la docencia y siempre en la búsqueda de programar cada vez mejor. Aunque a veces algo terco, sé que no todo en la vida es blanco o negro. Existe el Open Source, y lo respeto pero me llevo mejor con el .Net. Si me hablas de preferencias, te digo que C#, C++, una buena película, colores oscuros, escribir, leer e investigar. Para terminar les diré que amo muchísimo a una mujer espectacular y que es la dueña de mi corazón.


Comentarios (4)

Piero Alvarez dice:

Excelente guía, hace un tiempo estaba buscando información sobre esto precisamente y no encontré casi nada en castellano.

Angelo dice:

Tengo un problema con el backgroundWorker, lo que pasa es que estoy cargando un DataSet de una base de datos desde Access pero al momento que lo quiero mostrar en un listview se demora demasiado, por eso decidi utilizar esta herramienta. Lo primero que hago es cargar el dataset, luego lo pongo en un listview, pero me dice este error o advertencia : “Operación no válida a través de subprocesos: Se tuvo acceso al control ‘listView1′ desde un subproceso distinto a aquel en que lo creó.” Como podria solucionar este problema??

El problema está en que durante la ejecución del background worker no puedes utilizar componentes del formulario. Para solucionarlo utiliza el evento ReportProgress del background worker y dentro de este evento puedes modificar el listview1. Ahora solo tienes que invocar al evento reportProgress desde la ejecución del background worker.

andres dice:

muy buena informacion…los conocimientos que se puede adquirir en base a la programacion son ilimitados..esto basta para seguir investigando y aprendiendo todos los dias con el objetivo de ser mejor que hoy. saludos cordiales.

Deja un comentario

   

copstone en Facebook

Otros artículos

Uno mas uno, según las matemáticas es dos, pero si a un puntero le sumamos uno, cuánto sale?. Si tienes la respuesta o si tienes una duda, te invito a leer este post.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Los archivos bat son pequeños programas de Windows que podemos crear muy rápidamente y son muy útiles para realizar tareas repetitivas y tediosas. Se pueden crear archivos bat para borrar archivos temporales o cookies, copiar carpetas, realizar backups e inclusive para mandar archivos a través de una red. En este artículo voy a mostrar algunos trucos para la creación de archivos bat.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Si estas cansado de que tu aplicación se “congele” cuando realizas una función determinada, aquí te dejo una solución para este problema.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Muchas veces es necesario crear aplicaciones que permitan abrir otros programas o aplicaciones como por ejemplo hojas de cálculo, documentos de texto, reportes etc. En este artículo te voy a mostrar como abrir programas o archivos con C++.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Todas las ventanas de windows responden a mensajes como se explica en Como funciona el sistema de ventanas en windows. Si deseas que tu ventana responda de forma distinta ante un mensaje necesitas interceptarlo y eso lo voy a enseñar en este articulo.

FacebookGoogle BookmarksGoogle GmailTwitterYahoo MailHotmailLinkedInShare

Calendario

noviembre 2010
L M X J V S D
« jul   ene »
1234567
891011121314
15161718192021
22232425262728
2930  

Categorías

Comparte este artículo

  • Facebook
  • Google Bookmarks
  • Google Gmail
  • Twitter
  • Yahoo Mail
  • Hotmail
  • LinkedIn
  • Share
TIENES ALGO QUE PREGUNTAR? ESCRÍBENOS AQUÍ

Copyright © 2012 - Programando por diversion

Subir