Está en la página 1de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

PROCESOS Y SUBPROCESOS (HILOS O THREADS) EN WINDOWS ............................................... 2 ADMINISTRACIN DE PROCESOS .............................................................................................. 3 Iniciar Procesos ................................................................................................................. 3 Detener Procesos ............................................................................................................... 4 Determinar si un proceso responde ................................................................................... 4 Determinar si se ha salido de un proceso .......................................................................... 5 Ver los procesos en ejecucin ............................................................................................ 6 Esperar a que los procesos finalicen acciones .................................................................. 6 RECUPERAR DATOS DE PROCESOS .......................................................................................... 7 Uso de memoria por un proceso ........................................................................................ 7 Bibliotecas cargadas por un proceso................................................................................. 8 Subprocesos de un proceso ................................................................................................ 9 ADMINISTRACIN DE HEBRAS O HILOS ................................................................................... 9 Por qu son tiles las hebras? ....................................................................................... 10 La clase Thread................................................................................................................ 10 El control de las hebras ................................................................................................... 11 Estado y prioridad de las hebras ..................................................................................... 11 Ejemplo de hebras ............................................................................................................ 12 Sincronizacin de hebras ................................................................................................. 19 LAS CLASES DE SINCRONIZACIN .......................................................................................... 20 La clase Mutex ................................................................................................................. 20 La clase Monitor .............................................................................................................. 21 La clase Semaphore ......................................................................................................... 22

Pgina 1 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Procesos y Subprocesos (hilos o threads) en Windows


Un proceso lo definimos como una ocurrencia de un programa en ejecucin. Aunque la palabra instancia en castellano, tiene otros significados a los utilizados en informtica, se acepta llamar instancia (del ingls instance) a la ocurrencia de un programa en ejecucin. En Win32, un proceso es propietario de un espacio de direcciones de 4 GB (gigabytes). Esto quiere decir, que el proceso puede utilizar como si existiese realmente una memoria de 4 GB para su ejecucin. Al contrario que sus viejos predecesores: MsDOS y sistemas operativos Windows de 16 bits, los procesos en Win32 son inertes: es decir, un proceso Win32 no ejecuta nada. Es simplemente propietario de un espacio de direcciones de 4 GBs que contiene el cdigo y datos para un fichero de aplicacin .EXE. Cualquier DLL requerida por el EXE tambin tiene su parte de cdigo y datos en el espacio de direcciones del proceso. Adems del espacio de direcciones, un proceso tambin es propietario de ciertos recursos: ficheros, asignaciones dinmicas de memoria, hilos (threads), etc. Todos los recursos creados durante la vida de un proceso son destruidos cuando el proceso termina. Esto est garantizado. Tal y como hemos comentado, un proceso es inerte. Para que un proceso sea capaz de hacer algo, el proceso debe ser propietario de un hilo (thread). A partir de ahora utilizaremos el termino thread ya que es el que normalmente se utiliza en programacin. Este thread es el responsable de ejecutar el cdigo contenido en el espacio de direcciones del proceso. De echo, un proceso puede contener varios threads y todos ellos ejecutando cdigo simultneamente en el espacio de direcciones del proceso. Para ello, cada thread tiene su propia coleccin de registros de la CPU y su propio stack (pila de almacenamiento). Cada proceso tiene al menos un thread que ejecuta el cdigo contenido en el espacio de direcciones del proceso. Si no hay threads ejecutando cdigo en el espacio de direcciones del proceso no hay ninguna razn para que el proceso contine existiendo y el sistema destruir automticamente el proceso y su espacio de direcciones. Para todos los threads en ejecucin, el sistema operativo planifica un pequeo tiempo de CPU para cada thread individual. El sistema operativo, nos crea la ilusin de que todos los threads se ejecutan concurrentemente al asignar rodajas de tiempo (time slices) llamadas quantums a cada thread alternativamente (y consecutivamente). En principio, el tiempo de cada quantum es de 15 milisegundos. Es decir, si el sistema operativo asigna un quantum completo a cada hilo, en un segundo podrn ejecutarse 66 hilos que nos parecer que se ejecutan simultneamente. Recordemos que en una maquina con solo una CPU, nicamente podr estar activo un hilo en cada instante de tiempo. Cuando se crea un proceso Win32, su primer thread, llamado thread primario, se crea automticamente por el sistema. Este thread, a su vez, puede crear threads adicionales y estos, a su vez, pueden volver a crear ms thread. Windows NT y Windows 2000, son capaces de utilizar maquinas multiprocesador (con varias CPUs). En este caso, el sistema operativo es capaz de asignar en cada instante de tiempo, una CPU a cada hilo y por tanto, pueden existir dos o ms threads ejecutndose simultneamente. No se necesita realizar nada especial en el cdigo para poder utilizar las ventajas de una maquina multiprocesador.

Pgina 2 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

La familia de Windows 9x, puede utilizar nicamente un procesador. Aunque la maquina en la que se est ejecutando un Windows 9x contenga ms de un procesador, Windows 9x va a planificar nicamente un thread en cada instante. El otro procesador estar durmiendo todo el tiempo.

Administracin de procesos
En la plataforma .NET disponemos para la administracin de procesos de la clase Process, clase que hereda de Component. Por tanto Process es un componente que nos permite iniciar, detener, controlar y supervisar aplicaciones. Para poder utilizar dicha clase en nuestros programas necesitamos incluir el espacio de nombres System.Diagnostics. Como cualquier otra clase, Process contiene una serie de miembros, mtodos y propiedades, que ustedes pueden ver en la ayuda del entorno, en este tema veremos algunos de estos miembros a medida que los vayamos necesitando.

Iniciar Procesos
Puede utilizar el componente Process para iniciar procesos en el sistema mediante una llamada al mtodo Start. Para llamar a Start, primero debe especificar el nombre de archivo del proceso que se va a iniciar, estableciendo la propiedad FileName en la ruta de acceso completa al proceso de destino, o, en el caso de aplicaciones cualificadas para Windows, como Notepad, solo el nombre de proceso. Puede establecer la propiedad FileName en tiempo de diseo, utilizando la ventana Propiedades, o en tiempo de ejecucin, utilizando un valor de la enumeracin StartInfo. Si establece el nombre de archivo en tiempo de ejecucin, puede hacer uno de lo siguiente: Establecer el valor adecuado de la enumeracin StartInfo y luego llamar a Start, o Llamar a la forma esttica del mtodo Start y especificar FileName como parmetro. Utilice este mtodo si no necesita establecer ms parmetros de inicio; no puede establecer ningn otro argumento de apertura en este mtodo.

Para iniciar un proceso en tiempo de ejecucin utilizando las propiedades StartInfo 1. Establezca la informacin de inicio expuesta mediante la propiedad StartInfo. 2. Llame al mtodo Start del componente Process. El ejemplo siguiente muestra cmo se abre Notepad en una ventana maximizada.
Process myProcess = new Process(); myProcess.StartInfo.FileName = "Notepad"; myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized; myProcess.Start();

Para iniciar un proceso en tiempo de ejecucin pasando el parmetro Filename Llame al mtodo Start y escriba el parmetro FileName en forma de expresin de cadena.

Pgina 3 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Process myProcess = Process.Start("Notepad");

Detener Procesos
Pueden utilizarse dos mtodos para detener un proceso con un componente Process. El mtodo que se utilice depende del tipo de proceso que se va a detener. Si el proceso tiene una interfaz grfica para el usuario, llame al mtodo CloseMainWindow. Este mtodo enva una solicitud de cierre a la ventana principal del proceso y se comporta igual que si se seleccionara el comando Cerrar en la barra de tareas de la interfaz de usuario. El uso de este mtodo proporciona al programa de destino la oportunidad de solicitar al usuario que guarde los datos no guardados durante la operacin de limpieza. el proceso no tiene interfaz de usuario, llame al mtodo Kill. Precaucin Si llama al mtodo Kill, el proceso se detendr inmediatamente sin solicitar al usuario que guarde los datos modificados. Los datos no guardados se perdern. Si desea que el componente reciba una notificacin cuando el sistema operativo haya cerrado un proceso, debe establecer la propiedad EnableRaisingEvents en true. La propiedad EnableRaisingEvents se utiliza en procesos asincrnicos para notificar a la aplicacin que se ha salido de un proceso. Para detener un proceso 1. Llame al mtodo GetProcessesByName para recuperar el proceso que desee detener. 2. Llame a uno de los mtodos siguientes: Si el proceso tiene interfaz de usuario, llame al mtodo CloseMainWindow. Si el proceso no tiene ventanas, llame al mtodo Kill. El ejemplo siguiente muestra cmo se llama al mtodo CloseMainWindow para cerrar todas las instancias de Notepad que se estn ejecutando en un equipo local:
Process[] myProcesses; // Devuelve un array con todas las instancias de Notepad. myProcesses = Process.GetProcessesByName("Notepad"); foreach(Process myProcess in myProcesses) { myProcess.CloseMainWindow(); }

Determinar si un proceso responde


Puede utilizar la propiedad Responding para determinar si la interfaz de usuario de un proceso responde. Cuando intenta leer la propiedad Responding, se enva una solicitud a la interfaz de usuario del proceso de destino. Si hay una respuesta inmediata, el valor devuelto de la propiedad es true; si no hay respuesta de la interfaz, se devuelve un valor de propiedad false. Esta propiedad es til si se necesita forzar el cierre de una propiedad congelada.

Pgina 4 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Para determinar si un proceso responde 1. Si el proceso no ha sido iniciado por un componente, asocie un componente Process al proceso de destino. 2. Llame al mtodo Responding para leer la propiedad Responding. 3. Determine el curso de accin a emprender sobre la base del valor de la propiedad. El ejemplo siguiente muestra cmo se determina si Notepad responde. Si el valor de la propiedad Response es true, llame al mtodo CloseMainWindow para cerrar la aplicacin. Si el valor de la propiedad Response es false, se llama al mtodo Kill para forzar el cierre del proceso.
Process[] notepads; notepads = Process.GetProcessesByName("Notepad.exe"); // Comprobar si el proceso responde. if (notepads[0].Responding) { notepads[0].CloseMainWindow(); } else { notepads[0].Kill(); }

Determinar si se ha salido de un proceso


Puede utilizar la propiedad HasExited para determinar si el proceso asociado a un componente Process ha detenido su ejecucin. El valor de la propiedad devuelve true si el proceso est cerrado y false si contina en ejecucin. Nota Este valor slo se devuelve para procesos iniciados por un componente Process. El componente Process no necesita cerrar el proceso asociado para obtener la propiedad HasExited. La informacin administrativa, tal como las propiedades HasExited y ExitTime, se almacena independientemente de cmo se haya cerrado el proceso asociado. La informacin se almacena aunque el usuario seleccione el comando Cerrar de la interfaz para cerrar el proceso. Esta informacin es til si desea asegurarse de que todos los procesos iniciados por los componentes Process se cierran al salir de una aplicacin. Para determinar si se ha salido de un proceso Lea la propiedad HasExited del componente Process utilizado para abrir el proceso. El ejemplo siguiente muestra cmo se utiliza la propiedad HasExited para determinar si se ha cerrado el proceso asociado a un componente Process denominado notepad. Si est abierto, llama a CloseMainWindow para cerrar la aplicacin.
if (!notepad.HasExited) { notepad.CloseMainWindow(); }

Pgina 5 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Tambin podramos querer que se ejecutase algn cdigo cuando se terminase el proceso, para ello podemos hacer uso del evento Exited, veamos un ejemplo: Process prueba = new Process(); prueba.StartInfo.FileName = "Calc"; prueba.EnableRaisingEvents = true; prueba.Exited += new EventHandler(prueba_Exited); prueba.Start(); . . private void prueba_Exited(object sender, EventArgs e) { MessageBox.Show("He salido"); }

Ver los procesos en ejecucin


Cuando se trabaja con procesos en un sistema, puede ser necesario ver todos los procesos que se estn ejecutando en un momento dado. Por ejemplo, si desea crear una aplicacin que proporcione la funcionalidad de detener procesos, deber ver en primer lugar qu procesos se estn ejecutando. Puede llenar un cuadro de lista con los nombres de los procesos y seleccionar el proceso en el que desee ejecutar otras acciones. Para ver los procesos que estn en ejecucin 1. Declare una matriz vaca del tipo Process. 2. Llene la matriz vaca con el valor devuelto por el mtodo GetProcesses. 3. Itere en la matriz de procesos utilizando el valor indizado para obtener el nombre de cada proceso de la matriz y escribirlo en una consola. El ejemplo siguiente muestra cmo se llama al mtodo GetProcesses de un componente Process para devolver la matriz de procesos y se escribe el valor ProcessName en una consola.
Process[] myProcesses = Process.GetProcesses(); foreach(Process myProcess in myProcesses) { Console.WriteLine(myProcess.ProcessName); }

Esperar a que los procesos finalicen acciones


Se dice que un proceso est inactivo cuando su ventana principal est a la espera de una entrada por parte del sistema. Para comprobar el estado de inactividad de un proceso, deber en primer lugar enlazarle un componente Process. Puede llamar al mtodo WaitForInputIdle antes de hacer que el proceso de destino ejecute una accin. El mtodo WaitForInputIdle indica a un componente Process que espere a que el proceso asociado entre en estado de inactividad. Este mtodo es til, por ejemplo, cuando la aplicacin espera a que un proceso termine de crear la ventana principal antes de comunicarse con ella. El mtodo WaitForInputIdle slo funciona con procesos que tengan una interfaz de usuario. Para esperar a que un proceso complete una accin

Pgina 6 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

1. Asocie una instancia de un componente Process al proceso que desee iniciar. Para obtener ms informacin, vea Iniciar procesos. 2. Llame al mtodo Start para iniciar el proceso. 3. Llame al mtodo WaitForInputIdle adecuado: WaitForInputIdle() - indica al componente Process que espere indefinidamente a que el proceso asociado entre en estado de inactividad. WaitForInputeIdle(Int32) - indica al componente Process que espere durante el nmero especificado de milisegundos a que el proceso asociado entre en estado de inactividad.

El ejemplo siguiente muestra cmo se llama al mtodo WaitForInputIdle para esperar a que termine de cargarse Notepad antes de asignar la propiedad de mdulos a una matriz vaca.
Process myProcess; myProcess = Process.Start("Notepad"); myProcess.WaitForInputIdle();

Recuperar Datos de Procesos


Podemos utilizar un componente Process para recuperar informacin sobre las propiedades de un proceso. As podemos obtener informacin, entre otras, sobre la memoria utilizada por el proceso, sobre las bibliotecas (DLL) que tiene abiertas o sobre los subprocesos.

Uso de memoria por un proceso


Si necesita ver las estadsticas de memoria de un proceso, el componente Process proporciona seis propiedades de uso de memoria con acceso en tiempo de ejecucin. Cada propiedad proporciona una estadstica diferente de asignacin de memoria. Para investigar el uso de memoria de un proceso Enlace una instancia del componente Process al proceso. Si es necesario actualizar la cach de propiedades, llame al mtodo Refresh. Para leer la propiedad de uso de memoria que desee, haga referencia a la propiedad en cuestin. Propiedad WorkingSet64 PeakWorkingSet64 VirtualMemorySize64 PeakVirtualMemorySize64 PrivateMemorySize64 Valores devueltos Obtiene el tamao de la memoria fsica que se ha asignado al proceso asociado. Obtiene el tamao mximo de la memoria fsica que utiliza el proceso asociado. Obtiene el tamao de la memoria virtual que se ha asignado al proceso. Obtiene el tamao mximo de la memoria virtual que utiliza el proceso asociado. Nmero de bytes asignados al proceso asociado que no se pueden compartir con otros procesos.

Pgina 7 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

PeakPagedMemorySize64

Obtiene el tamao de memoria mximo en el archivo de paginacin de la memoria virtual que utiliza el proceso asociado. PagedSystemMemorySize64 Obtiene el tamao de la memoria paginable del sistema que se ha asignado para el proceso asociado. PagedMemorySize64 Obtiene el tamao de la memoria paginada asignada para el proceso asociado. NonpagedSystemMemorySize64 Obtiene el tamao de la memoria no paginada del sistema que se ha asignado para el proceso asociado. El ejemplo siguiente muestra cmo se utiliza el componente Process para leer la propiedad PeakWorkingSet64 para Notepad y se asigna el valor devuelto de la propiedad a la variable memory. El valor se muestra a continuacin en una consola. Dado que Component1(0) es una instancia nueva del componente Process, no es necesario actualizar la cach de la propiedad.
int memory; Process[] notepads; notepads = Process.GetProcessesByName("Notepad.exe"); memory = notepads[0]. PeakWorkingSet64; Console.WriteLine("Memory used: {0}.", memory);

Bibliotecas cargadas por un proceso


La propiedad Modules del componente Process proporciona acceso a las bibliotecas cargadas para un proceso. La propiedad Modules devuelve una coleccin del tipo ProcessModules, que incluye todas las bibliotecas cargadas para el proceso de destino. A continuacin, puede iterar en la coleccin para ver bibliotecas individuales mediante la propiedad Indexed. Para investigar el uso de bibliotecas de un proceso 1. Si el proceso de destino no ha sido iniciado por un componente Process, enlace una instancia nueva de un componente Process al proceso. 2. Declare una matriz vaca de tipo ProcessModules para que contenga la coleccin de mdulos. 3. Asigne la propiedad Modules a la variable ProcessModules. De este modo se llenar la matriz ProcessModules con los mdulos procedentes del mdulo de destino. 4. Itere en la matriz ProcessModules utilizando la propiedad Array(Index) para ver y administrar bibliotecas individuales. El ejemplo siguiente muestra cmo se devuelven todas las bibliotecas cargadas para Microsoft Word y se pasa a continuacin el nombre de la biblioteca a un cuadro de lista de Form1:
Process[] wordapps; ProcessModuleCollection modules; wordapps = Process.GetProcessesByName("WinWord"); // Populate the module collection. modules = wordapps[0].Modules;

Pgina 8 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

// Iterate through the module collection. foreach (ProcessModule aModule in modules) { Form1.Listbox1.Items.Add(aModule.ModuleName); }

Subprocesos de un proceso
Para ver los subprocesos del proceso puede leer el valor de la propiedad Threads del componente Process. El valor devuelto es una coleccin del tipo ProcessThread que representa los subprocesos del sistema operativo que estn en ejecucin en el proceso. A continuacin, puede iterar en la matriz para ver propiedades individuales de subprocesos mediante la propiedad Array(Index). El subproceso principal no es necesariamente el subproceso que se encuentra en el ndice 0 de la matriz. Para investigar el uso de subprocesos de un proceso 1. Si el proceso no ha sido iniciado por un componente Process, asocie un componente Process al proceso deseado. 2. Asigne el valor de la propiedad Threads del proceso a una variable de coleccin vaca de tipo ProcessThread.

3. Itere en el ndice de la matriz para ver las propiedades de cada subproceso individual. El ejemplo siguiente muestra cmo se lee la propiedad Threads de Notepad y se asigna el valor a una matriz vaca. El valor Thread.BasePriority del primer subproceso de la matriz ProcessThread se lee a continuacin y se muestra en un cuadro de texto denominado TextBox1.
ProcessThreadCollection threads; Process[] notepads; //Devuelve los procesos de Notepad. notepads = Process.GetProcessesByName("Notepad"); // Lee la propiedad Process.Threads que hace referencia a la coleccin de hilos. threads = notepads[0].Threads; //Lee la propiedad BasePriority. This.Textbox1.Text = threads[0].BasePriority.ToString()

Administracin de Hebras o Hilos


El mbito System.Threading proporciona en la plataforma .NET las clases e interfaces para escribir cdigo multihebra. Al principio del tema ya hablamos del concepto de hebra pero recordemos, en windows cualquier proceso consta de al menos una hebra que ejecuta la funcin principal y, si no se crean ms, ser un programa de hebra simple. Si el proceso crea ms hebras, ser multihebra. Cada hebra con un proceso tiene datos asociados con l que incluyen su propia pila del programa y el conjunto de contenidos del registro, conocido como contexto de la hebra. Cuando el sistema operativo necesita cambiar entre hebras, debe guardar el contexto de la

Pgina 9 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

hebra actual y cargar en memoria el contexto de la hebra que se va a cargar a continuacin. Este proceso se llama conmutacin de contexto y aade una sobrecarga de proceso al sistema operativo.

Por qu son tiles las hebras?


Las hebras son tiles en muchas circunstancias, veamos algunas. En primer lugar, cuando hay una tarea de fondo. Imagine que quiere poner un logotipo que gire en la parte superior izquierda del formulario. Tiene una secuencia de imgenes, por lo tanto, quiere mostrar cada una de ellas durante un corto espacio de tiempo. Cmo va a integrar esto en el resto de la aplicacin?. Sera estupendo si pudiera hacer un bucle que muestre las imgenes mientras se ejecuta el resto del programa. La ejecucin del cdigo que muestra la imagen en una hebra separada permite hacer esto. Otro ejemplo es la impresin en segundo plano: si su aplicacin quiere imprimir, y no quiere que el usuario tenga que esperar mientras se envan los datos a la impresora. En segundo lugar, cuando esta realizando ms de una tarea a la vez. Suponga que tiene un programa de procesado de imgenes que toma una imagen y realiza alguna operacin con ella. Podra dividir la imagen en cuatro partes y ejecutar las cuatro copias en la rutina de procesado de imagen, en la que cada rutina procesa un cuarto de la imagen. Esto puede implicar prdidas de tiempo en mquinas multiprocesador, pero sera bastante til en mquinas de un solo procesador. En tercer lugar, hay otros casos en los que el uso de hebras es el camino normal para estructurar un programa. Considere el caso de un servidor Web o de correo que mira un puerto esperando que el cliente se conecte. El servidor puede manejar ms de un cliente, por lo que, cmo manejar al primer cliente mientras tambin espera al segundo cliente?. Es difcil por el hecho de que esperar en un puerto es una operacin de bloqueo, as que el servidor no puede dejarlo y hacer otra cosa fcilmente. La respuesta es utilizar una hebra separada para manejar cada cliente. Cuando un cliente se conecta, el servidor inicia una hebra para manejar la sesin con el cliente y, a continuacin, realiza el bucle y espera al prximo cliente.

La clase Thread
La clase System.Threading.Thread representa una hebra del sistema operativo. Algunas de las propiedades y mtodos ms utilizados de esta clase son: Propiedad CurrentPrincipal CurrentThread IsAlive IsBackground Name Priority Descripcin Obtiene o pone la seguridad principal actual de la hebra. Obtiene una referencia a la hebra que se esta ejecutando. Devuelve verdadero si la hebra ha comenzado y no ha muerto. Es verdadero si esta hebra es una hebra de segundo plano. Obtiene o pone el nombre de esta hebra. Obtiene o pone la prioridad para esta hebra.

Pgina 10 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

ThreadState Mtodo Abort Interrupt Join Resume Sleep Start Suspend

Obtiene el estado de esta hebra. Descripcin Elimina la hebra. Interrumpe una hebra que esta en el estado WaitSleepJoin. Espera a que una hebra termine. Reanuda una hebra suspendida. Enva una hebra a dormir durante un periodo de tiempo. Inicia una hebra. Suspende una hebra.

El control de las hebras


Una vez que ha creado el objeto Thread y ha pasado la direccin de una funcin a ejecutar, llame a su mtodo Start() para comenzar la ejecucin. La funcin hebra se ejecuta y, cuando vuelve, la hebra finaliza su ejecucin. La hebra del sistema operativo entonces muere, incluso aunque el objeto Thread an exista. Puede comprobar el estado de la hebra usando la propiedad IsAlive para ver si ha muerto. Si quiere terminar una hebra puede llamar al mtodo Abort(). Tenga cuidado cuando llame a este mtodo, pues dependiendo de la situacin podra producir datos corruptos, por ejemplo si estuviera en mitad de una actualizacin o escribiendo un archivo. Los mtodos Suspend() y Resume() se pueden utilizar para parar temporalmente la ejecucin de una hebra y a continuacin reiniciarla de nuevo. Con estos mtodos puede surgir el mismo problema que con el mtodo Abort(). Sleep() se utiliza para poner una hebra a dormir durante un periodo de tiempo, normalmente especificado como un nmero de milisegundos. Este es un mtodo muy til porque cuando una hebra duerme no consume tiempo del procesador. Una hebra que duerme se puede interrumpir, por ejemplo, terminando el programa o al apagar la mquina.

Estado y prioridad de las hebras


La enumeracin ThreadState, describe los posibles estados que puede tener una hebra: Elemento Aborted AbortRequested Background Running Stopped StopRequested Suspended SuspendRequested Unstarted WaitSleepJoin Descripcin La hebra se ha abortado y ahora esta muerta. La hebra se esta pidiendo para abortar. La hebra se esta ejecutando como una hebra en segundo plano. La hebra se esta ejecutando. La hebra esta parada (el valor es solo para uso interno). La hebra se esta pidiendo para parar (el valor es solo para uso interno). La hebra se ha suspendido. La hebra se esta pidiendo para suspenderse. La hebra an no ha comenzado. La hebra esta bloqueada en una llamada a Wait(), Sleep() o Join().

Pgina 11 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Antes de llamar al mtodo Start(), una hebra esta Unstarted y, a continuacin, pasa al estado Running. El mtodo Resume() pone a la hebra en el estado Suspend, y la siguiente llamada a Resume(), la devuelve al estado Running. La propiedad IsAlive devuelve verdadero si la hebra ha comenzado y todava no ha muerto, es decir, si esta en los estados Running, Background, Suspended, SuspendRequested o WaitSleepJoin. Una hebra en primer plano se ejecuta indefinidamente, mientras que una hebra en segundo plano termina cuando la ltima hebra en primer plano ha parado. Suele ser til hacer hebras que comiencen en segundo plano para nuestra aplicacin, porque automticamente se cortarn cuando termine el programa. Puede usar la propiedad IsBackground para cambiar el estado de primer o segundo plano de una hebra. Cada hebra tiene una prioridad relativa a las otras en el proceso. Por defecto, las hebras se crean con una prioridad media, y puede ajustar la prioridad de la hebra asignando un nuevo valor a la propiedad Priority del objeto Thread. Esta toma un elemento de la enumeracin ThreadPriority, cuyos valores son: Elemento Hightest AboveNormal Normal. BelowNormal Lowest Descripcin La hebra tiene la prioridad ms alta. La hebra tiene una prioridad mayor que la normal. La hebra tiene una prioridad normal. La hebra tiene una prioridad ms baja que la normal. La hebra tiene la prioridad ms baja.

Todas las hebras, cuando se crean inicialmente, tienen una prioridad Normal. Tenga cuidado con jugar mucho con las prioridades de las hebras. El sistema operativo utiliza las prioridades para decidir cuando se ejecutan estas y los algoritmos usados pueden ser complejos. Esto significa que ajustando las prioridades de las hebras puede que no siempre devuelva los resultados deseados.

Ejemplo de hebras
Veamos algunos de los conceptos vistos anteriormente con un ejemplo. Este consiste en dos hebras, una que representa en un pictureBox de manera alternativa la cara alegre y la cara triste y la otra que es un contador. Ambas hebras permiten ser iniciadas, paradas y reanudadas con los mtodos Start(), Supsend() y Resume() respectivamente. Adems se puede comprobar mediante el checkBox como funciona el Join(). Cuando esta marcado, la segunda hebra no empezara a ejecutarse hasta que no termine la primera. Aqu podemos ver el formulario con ambas hebras en ejecucin y el cdigo del programa, pero el cdigo fuente tambin lo podis descargar de la pgina web del instituto.

Pgina 12 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

using using using using using using using

System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; System.Threading;

namespace Hebras2 { /// <summary> /// Descripcin breve de Form1. /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.PictureBox pctImagen; private System.Windows.Forms.Button btnIniciar1; /// <summary> /// Variable del diseador requerida. /// </summary> private System.ComponentModel.Container components = null; private System.Windows.Forms.Button btnSuspender1; private System.Windows.Forms.Button btnReanudar1; private System.Windows.Forms.Button btnParar1; private System.Windows.Forms.Label lbEstado1; //Declaracin de hebras Thread Hilo1,MiraHilo,Hilo2; private private private private private private private System.Windows.Forms.Label lbContador; System.Windows.Forms.Button btnIniciar2; System.Windows.Forms.Button btnSuspender2; System.Windows.Forms.Button btnReanudar2; System.Windows.Forms.Button btnParar2; System.Windows.Forms.CheckBox checkBox1; System.Windows.Forms.Label lbEstado2;

//Delegados para acceder a los controles desde las hebras delegate void RellamadaPonerLabel(string cadena); RellamadaPonerLabel pl;

Pgina 13 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

delegate void PonerEstados(string c1, string c2); PonerEstados p2; public Form1() { // // Necesario para admitir el Diseador de Windows Forms // InitializeComponent(); //Inicializacin de delegados pl = new RellamadaPonerLabel(PonerLabel); p2 = new PonerEstados (PonerEstado); //Inicializacin e hebras Hilo1=new Thread(new ThreadStart(Ejecutar1)); Hilo1.IsBackground=true; Hilo2=new Thread(new ThreadStart(Ejecutar2)); Hilo2.IsBackground=true; MiraHilo = new Thread(new ThreadStart(Mirar)); MiraHilo.IsBackground = true; MiraHilo.Start(); } //Funciones de delegados public void PonerLabel(string s) { lbContador.Text=s; } public void PonerEstado(string s1, string s2) { this.lbEstado1.Text = s1; this.lbEstado2.Text = s2; } /// <summary> /// Limpiar los recursos que se estn utilizando. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Cdigo generado por el Diseador de Windows Forms /// <summary> /// Mtodo necesario para admitir el Diseador. No se puede modificar /// el contenido del mtodo con el editor de cdigo. /// </summary> private void InitializeComponent() { this.pctImagen = new System.Windows.Forms.PictureBox(); this.btnIniciar1 = new System.Windows.Forms.Button(); this.btnSuspender1 = new System.Windows.Forms.Button(); this.btnReanudar1 = new System.Windows.Forms.Button(); this.btnParar1 = new System.Windows.Forms.Button(); this.lbEstado1 = new System.Windows.Forms.Label();

Pgina 14 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

this.lbContador = new System.Windows.Forms.Label(); this.btnIniciar2 = new System.Windows.Forms.Button(); this.btnSuspender2 = new System.Windows.Forms.Button(); this.btnReanudar2 = new System.Windows.Forms.Button(); this.btnParar2 = new System.Windows.Forms.Button(); this.lbEstado2 = new System.Windows.Forms.Label(); this.checkBox1 = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)(this.pctImagen)).BeginInit(); this.SuspendLayout(); // // pctImagen // this.pctImagen.Location = new System.Drawing.Point(32, 16); this.pctImagen.Name = "pctImagen"; this.pctImagen.Size = new System.Drawing.Size(100, 72); this.pctImagen.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; this.pctImagen.TabIndex = 0; this.pctImagen.TabStop = false; // // btnIniciar1 // this.btnIniciar1.Location = new System.Drawing.Point(40, 120); this.btnIniciar1.Name = "btnIniciar1"; this.btnIniciar1.Size = new System.Drawing.Size(75, 23); this.btnIniciar1.TabIndex = 1; this.btnIniciar1.Text = "Iniciar"; this.btnIniciar1.Click += new System.EventHandler(this.btnIniciar1_Click); // // btnSuspender1 // this.btnSuspender1.Location = new System.Drawing.Point(40, 168); this.btnSuspender1.Name = "btnSuspender1"; this.btnSuspender1.Size = new System.Drawing.Size(75, 23); this.btnSuspender1.TabIndex = 2; this.btnSuspender1.Text = "Suspender"; this.btnSuspender1.Click += new System.EventHandler(this.btnSuspender1_Click); // // btnReanudar1 // this.btnReanudar1.Location = new System.Drawing.Point(40, 216); this.btnReanudar1.Name = "btnReanudar1"; this.btnReanudar1.Size = new System.Drawing.Size(75, 23); this.btnReanudar1.TabIndex = 3; this.btnReanudar1.Text = "Reanudar"; this.btnReanudar1.Click += new System.EventHandler(this.btnReanudar1_Click); // // btnParar1 // this.btnParar1.Location = new System.Drawing.Point(40, 272); this.btnParar1.Name = "btnParar1"; this.btnParar1.Size = new System.Drawing.Size(75, 23); this.btnParar1.TabIndex = 4; this.btnParar1.Text = "Parar"; this.btnParar1.Click += new System.EventHandler(this.btnParar1_Click); //

Pgina 15 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

// lbEstado1 // this.lbEstado1.Location = new System.Drawing.Point(40, 328); this.lbEstado1.Name = "lbEstado1"; this.lbEstado1.Size = new System.Drawing.Size(264, 23); this.lbEstado1.TabIndex = 5; // // lbContador // this.lbContador.Font = new System.Drawing.Font("Microsoft Sans Serif", 25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.lbContador.Location = new System.Drawing.Point(376, 24); this.lbContador.Name = "lbContador"; this.lbContador.Size = new System.Drawing.Size(100, 64); this.lbContador.TabIndex = 6; // // btnIniciar2 // this.btnIniciar2.Location = new System.Drawing.Point(384, 112); this.btnIniciar2.Name = "btnIniciar2"; this.btnIniciar2.Size = new System.Drawing.Size(75, 23); this.btnIniciar2.TabIndex = 7; this.btnIniciar2.Text = "Iniciar"; this.btnIniciar2.Click += new System.EventHandler(this.btnIniciar2_Click); // // btnSuspender2 // this.btnSuspender2.Location = new System.Drawing.Point(384, 168); this.btnSuspender2.Name = "btnSuspender2"; this.btnSuspender2.Size = new System.Drawing.Size(75, 23); this.btnSuspender2.TabIndex = 8; this.btnSuspender2.Text = "Suspender"; this.btnSuspender2.Click += new System.EventHandler(this.btnSuspender2_Click); // // btnReanudar2 // this.btnReanudar2.Location = new System.Drawing.Point(384, 216); this.btnReanudar2.Name = "btnReanudar2"; this.btnReanudar2.Size = new System.Drawing.Size(75, 23); this.btnReanudar2.TabIndex = 9; this.btnReanudar2.Text = "Reanudar"; this.btnReanudar2.Click += new System.EventHandler(this.btnReanudar2_Click); // // btnParar2 // this.btnParar2.Location = new System.Drawing.Point(384, 272); this.btnParar2.Name = "btnParar2"; this.btnParar2.Size = new System.Drawing.Size(75, 23); this.btnParar2.TabIndex = 10; this.btnParar2.Text = "Parar"; this.btnParar2.Click += new System.EventHandler(this.btnParar2_Click); // // lbEstado2 // this.lbEstado2.Location = new System.Drawing.Point(368, 328); this.lbEstado2.Name = "lbEstado2";

Pgina 16 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

this.lbEstado2.Size = new System.Drawing.Size(264, 23); this.lbEstado2.TabIndex = 11; // // checkBox1 // this.checkBox1.Location = new System.Drawing.Point(200, 120); this.checkBox1.Name = "checkBox1"; this.checkBox1.Size = new System.Drawing.Size(104, 24); this.checkBox1.TabIndex = 12; this.checkBox1.Text = "Join"; // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(680, 374); this.Controls.Add(this.checkBox1); this.Controls.Add(this.lbEstado2); this.Controls.Add(this.btnParar2); this.Controls.Add(this.btnReanudar2); this.Controls.Add(this.btnSuspender2); this.Controls.Add(this.btnIniciar2); this.Controls.Add(this.lbContador); this.Controls.Add(this.lbEstado1); this.Controls.Add(this.btnParar1); this.Controls.Add(this.btnReanudar1); this.Controls.Add(this.btnSuspender1); this.Controls.Add(this.btnIniciar1); this.Controls.Add(this.pctImagen); this.Name = "Form1"; this.Text = "Ejemplo de Hebras"; ((System.ComponentModel.ISupportInitialize)(this.pctImagen)).EndInit(); this.ResumeLayout(false); } #endregion /// <summary> /// Punto de entrada principal de la aplicacin. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } private void btnIniciar1_Click(object sender, System.EventArgs e) { Hilo1.Start(); } public void Ejecutar1() { while(true) { this.pctImagen.Image = Image.FromFile(@"C:\Archivos de programa\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary\VS2005ImageLibrary\bitmaps\ misc\input.bmp"); Thread.Sleep(1000);

Pgina 17 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

this.pctImagen.Image = Image.FromFile(@"C:\Archivos de programa\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary\VS2005ImageLibrary\bitmaps\ misc\output.bmp"); Thread.Sleep(1000); } } private void btnSuspender1_Click(object sender, System.EventArgs e) { Hilo1.Suspend(); } private void btnReanudar1_Click(object sender, System.EventArgs e) { Hilo1.Resume(); } private void btnParar1_Click(object sender, System.EventArgs e) { Hilo1.Abort(); } public void Mirar() { while(true) { this.Invoke(p2, new object[] { Hilo1.ThreadState.ToString(), Hilo2.ThreadState.ToString() }); Thread.Sleep(300); } } private void btnIniciar2_Click(object sender, System.EventArgs e) { Hilo2.Start(); } private void btnSuspender2_Click(object sender, System.EventArgs e) { Hilo2.Suspend(); } private void btnReanudar2_Click(object sender, System.EventArgs e) { Hilo2.Resume(); } private void btnParar2_Click(object sender, System.EventArgs e) { Hilo2.Abort(); } public void Ejecutar2() { if(this.checkBox1.Checked) Hilo1.Join(); while(true) { for(int i=0;i<10;i++) { this.Invoke(pl, new object[] { i.ToString() });

Pgina 18 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

Thread.Sleep(200); } } } } }

En este ejemplo adems de ver la utilizacin de hebras, se estudia un problema, el del acceso a lo datos de un control desde una hebra distinta a la que lo creo. Para ello se utilizan delegados y funciones delegadas junto con el mtodo Invoke. Ms aclaraciones sobre este tema en clase. Por ltimo decir sobre hebras que si no necesitamos para nuestra aplicacin demasiado control sobre la hebra, la mejor solucin es utilizar hebras administradas mediante la clase ThreadPool, estos subprocesos, con un mximo predeterminado de 25 por proceso, son gestionados por el sistema. Y a partir de la versin 2.0 tenemos la clase BackGroundWorker para lanzar subprocesos independientes y poder gestionar cuando lanzar el proceso y seguir su evolucin mediante los mtodos adecuados.

Sincronizacin de hebras
Puede que necesite sincronizar hebras por dos razones: debido al uso de los recursos compartidos y por la temporizacin. Cada hebra tiene su propia pila y conjunto de registros, lo que significa que cada funcin de la hebra tiene su propio conjunto de variables locales, puesto que las variables locales se declaran en la pila. Por lo que las variables locales no pueden interferir con otras, porque se crean en diferentes pilas. Las variables globales son un caso diferente porque pertenecen al proceso como un todo. Por lo tanto son accesibles para todas las hebras del proceso. Esto tiene problemas potenciales a causa del posible acceso por parte de dos hebras a la misma variable global, pudiendo generar la corrupcin del dato. Vemoslo en la figura.

HEBRA A
Establece el valor de la variable global X a 3
Conmutacin de contexto de la hebra A a la hebra B

HEBRA B

Conmutacin de contexto de la hebra B a la hebra A

Llamada a la funcin Func(X)

Establece el valor de la variable global X a 4

Pgina 19 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

La hebra A establece el valor de la variable global X a 3. En este punto, sucede una conmutacin de contexto y pasa a ejecutarse la hebra B; sta pone el valor de X a 4. Cuando la hebra A retoma el turno; usa el valor de la variable X, desconociendo que la hebra B ha cambiado su valor. Estos errores son difciles de ver porque suelen depender del tiempo. Si la prxima vez que ejecute el programa, la conmutacin entre hebras no ocurre en el mismo punto exactamente, puede que no aparezca el error. Este problema de recursos compartidos no se restringe a las variables globales, puede ocurrir con cualquier recurso que se comparta entre hebras como ficheros y tablas de bases de datos. Para evitar estos problemas se hace uso de banderas y semforos para obtener el uso exclusivo de un recurso.

Las clases de sincronizacin


El mbito System.Threading contiene varias clases que ayudan a la sincronizacin de hebras. La clase Interlocked contiene cuatro mtodos de hebra segura compartidos para realizar operaciones con variables. Cuatro de estos mtodos son atmicos, por lo que no se pueden interrumpir por cambios de contexto de hebras: Increment: Incrementa una variable. Decrement: Disminuye una variable. Exchange: Pone una variable a un valor y devuelve el valor original. CompareExchange: Compara dos valores y reemplaza el valor destino si son iguales.

Se deja al alumno la profundizacin en el uso de esta clase y se exigir un ejemplo de la misma en clase. Veamos otras clases de sincronizacin como Mutex y Monitor.

La clase Mutex
Mutex proporciona un mecanismo de sincronizacin simple que permite que una de las hebras tenga acceso exclusivo a un recurso compartido. Las hebras intentan adquirir un mutex; uno lo lleva a cabo y los otros se bloquearn hasta que la hebra haya finalizado y lo libere. El siguiente cdigo muestra el uso de un mutex:
using System.Threading; //El mutex se usa para sincronizar dos hebras Mutex mtx = new Mutex(); //El mtodo AcquireData se ejecuta en una hebra public void AcquireData() { //Intenta obtener el Mutex mtx.WaitOne(); //Cdigo para adquirir los datos //Libera el Mutex mtx.ReleaseMutex()

Pgina 20 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

} //El mtodo UseData se ejecuta en una segunda hebra public void UseData() { //Intenta obtener el Mutex mtx.WaitOne(); //Cdigo para utilizar los datos //Libera el Mutex mtx.ReleaseMutex() }

El cdigo en la primera hebra llama a AcquireData(), que intenta adquirir el objeto mutex por medio de una llamada a WaitOne(). Si el mutex est disponible, esta llamada se devuelve inmediatamente y, entonces, la hebra llamada toma el mutex. La funcin, a continuacin, adquiere sus datos. Si durante este tiempo, la segunda hebra llama a UseData(), se bloquear entonces en la llamada a WaitOne() puesto que el mutex no est disponible. En algn punto AdquireData() finaliza su tarea y libera el mutex llamando a ReleaseMutex(). Esto provoca que la llamada a WaitOne() en UseData() se devuelve, por lo que los datos se pueden usar. Este proceso puede parecer simple, aunque hay muchos problemas sutiles que pueden surgir cuando las hebras se sincronizan de esta forma. Qu sucede si UseData() ha abierto un archivo o utilizado algn otro recurso que necesita AdquireData()?. Es posible que UseData() se bloquee a la espera de que AdquireData() libere el mutex, pero UseData() necesita el recurso que AdquireData() tiene antes que pueda completar su tarea y liberar el mutez. El resultado es un bucle sin salida, donde ambas hebras estn bloqueadas e incapaces de avanzar.

La clase Monitor
Los objetos Monitor exponen la capacidad de sincronizar el acceso a una regin de cdigo mediante la obtencin y liberacin de un bloqueo en un objeto en particular con los mtodos Monitor.Enter, Monitor.TryEnter y Monitor.Exit. Una vez que disponga de un bloqueo en una regin de cdigo, puede utilizar los mtodos Monitor.Wait, Monitor.Pulse y Monitor.PulseAll. Monitor bloquea objetos (es decir, tipos de referencia), no tipos de valor. Consideremos el siguiente ejemplo, un programa multihebra que usa un objeto ArrayList para almacenar un conjunto de valores de datos del programa. Las funciones estn disponibles para aadir datos a la lista, eliminar un valor de la lista, buscar en la lista un valor e imprimir la lista. El problema es que estas funciones se pueden llamar desde diferentes hebras, as que cmo puede estar seguro que las funciones de bsqueda o impresin no se estn modificando por las funciones de adicin o eliminacin de la lista a la misma vez?. Una solucin es usar un monitor, veamos el cdigo para la funcin de aadir:
//El mtodo Add se puede ejecutar en una hebra public void Add(Object o) { //monitorizamos el arralist Monitor.Enter(myArrayList); //Utilizamos la lista

Pgina 21 de 22

Procesos e Hilos de Windows en C#

Antonio Illana Vlchez

myArrayList.Add(o); //Libera el monitor Monitor.Exit(myArrayList); }

La clase Semaphore
Esta nueva clase de sincronizacin, que aparece en la versin ASP.NET 2.0, limita el nmero de subprocesos que pueden tener acceso a un recurso o grupo de recursos simultneamente. Para entrar en el semforo los subprocesos llaman al mtodo WaitOne y para liberarlo al mtodo Release. El funcionamiento es el siguiente, cuando se crea un semforo mediante el constructor Semaphore, se inicializa un contador que indica el nmero mximo de subprocesos que pueden coger el semforo. Cada vez que se ejecuta el mtodo WaitOne se decrementa en uno dicho valor, si este valor es cero el subproceso debe esperar a que un proceso deje libre el semforo con el mtodo Release . Para ms detalles miren ustedes la ayuda donde pueden ver un ejemplo.

Pgina 22 de 22

También podría gustarte