Proceso s

22
Procesos e Hilos de Windows en C# Antonio Illana Vílchez Página 1 de 22 PROCESOS Y SUBPROCESOS (HILOS O THREADS) EN WINDOWS ............................................... 2 ADMINISTRACIÓN 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 ejecución ............................................................................................ 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 ADMINISTRACIÓN 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 Sincronización de hebras ................................................................................................. 19 LAS CLASES DE SINCRONIZACIÓN .......................................................................................... 20 La clase Mutex ................................................................................................................. 20 La clase Monitor .............................................................................................................. 21 La clase Semaphore ......................................................................................................... 22

Transcript of Proceso s

Page 1: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 1 de 22

PROCESOS Y SUBPROCESOS (HILOS O THREADS) EN WINDOWS ............................................... 2 ADMINISTRACIÓN 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 ejecución ............................................................................................ 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

ADMINISTRACIÓN 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 Sincronización de hebras ................................................................................................. 19

LAS CLASES DE SINCRONIZACIÓN.......................................................................................... 20 La clase Mutex ................................................................................................................. 20 La clase Monitor .............................................................................................................. 21 La clase Semaphore ......................................................................................................... 22

Page 2: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 2 de 22

Procesos y Subprocesos (hilos o threads) en Windows Un proceso lo definimos como una ocurrencia de un programa en ejecución. Aunque la palabra ‘instancia’ en castellano, tiene otros significados a los utilizados en informática, se acepta llamar ‘instancia’ (del inglés ‘instance’) a la ocurrencia de un programa en ejecución. 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 ejecución. 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 GB’s que contiene el código y datos para un fichero de aplicación .EXE. Cualquier DLL requerida por el EXE también tiene su parte de código y datos en el espacio de direcciones del proceso. Además del espacio de direcciones, un proceso también es propietario de ciertos recursos: ficheros, asignaciones dinámicas 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 programación. Este thread es el responsable de ejecutar el código contenido en el espacio de direcciones del proceso. De echo, un proceso puede contener varios threads y todos ellos ejecutando código “simultáneamente” en el espacio de direcciones del proceso. Para ello, cada thread tiene su propia colección de registros de la CPU y su propio stack (pila de almacenamiento). Cada proceso tiene al menos un thread que ejecuta el código contenido en el espacio de direcciones del proceso. Si no hay threads ejecutando código en el espacio de direcciones del proceso no hay ninguna razón para que el proceso continúe existiendo y el sistema destruirá automáticamente el proceso y su espacio de direcciones. Para todos los threads en ejecución, el sistema operativo planifica un pequeño tiempo de CPU para cada thread individual. El sistema operativo, nos crea la ‘ilusión’ 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 podrán ejecutarse 66 hilos que nos parecerá que se ejecutan simultáneamente. 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 automáticamente por el sistema. Este thread, a su vez, puede crear threads adicionales y estos, a su vez, pueden volver a crear más thread. Windows NT y Windows 2000, son capaces de utilizar maquinas multiprocesador (con varias CPU’s). 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 más threads ejecutándose simultáneamente. No se necesita realizar nada especial en el código para poder utilizar las ventajas de una maquina multiprocesador.

Page 3: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 3 de 22

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

Administración de procesos En la plataforma .NET disponemos para la administración 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, métodos 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 método 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 diseño, utilizando la ventana Propiedades, o en tiempo de ejecución, utilizando un valor de la enumeración StartInfo. Si establece el nombre de archivo en tiempo de ejecución, puede hacer uno de lo siguiente: • Establecer el valor adecuado de la enumeración StartInfo y luego llamar a Start, o • Llamar a la forma estática del método Start y especificar FileName como parámetro.

Utilice este método si no necesita establecer más parámetros de inicio; no puede establecer ningún otro argumento de apertura en este método.

Para iniciar un proceso en tiempo de ejecución utilizando las propiedades StartInfo

1. Establezca la información de inicio expuesta mediante la propiedad StartInfo. 2. Llame al método Start del componente Process.

El ejemplo siguiente muestra cómo 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 ejecución pasando el parámetro Filename Llame al método Start y escriba el parámetro FileName en forma de expresión de cadena.

Page 4: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 4 de 22

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

Detener Procesos Pueden utilizarse dos métodos para detener un proceso con un componente Process. El método que se utilice depende del tipo de proceso que se va a detener. • Si el proceso tiene una interfaz gráfica para el usuario, llame al método

CloseMainWindow. Este método envía 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 método proporciona al programa de destino la oportunidad de solicitar al usuario que guarde los datos no guardados durante la operación de limpieza.

• el proceso no tiene interfaz de usuario, llame al método Kill.

Precaución Si llama al método Kill, el proceso se detendrá inmediatamente sin solicitar al usuario que guarde los datos modificados. Los datos no guardados se perderán.

Si desea que el componente reciba una notificación cuando el sistema operativo haya cerrado un proceso, debe establecer la propiedad EnableRaisingEvents en true. La propiedad EnableRaisingEvents se utiliza en procesos asincrónicos para notificar a la aplicación que se ha salido de un proceso. Para detener un proceso

1. Llame al método GetProcessesByName para recuperar el proceso que desee detener. 2. Llame a uno de los métodos siguientes:

• Si el proceso tiene interfaz de usuario, llame al método CloseMainWindow. • Si el proceso no tiene ventanas, llame al método Kill.

El ejemplo siguiente muestra cómo se llama al método CloseMainWindow para cerrar todas las instancias de Notepad que se están 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 envía 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.

Page 5: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 5 de 22

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 método Responding para leer la propiedad Responding. 3. Determine el curso de acción a emprender sobre la base del valor de la propiedad.

El ejemplo siguiente muestra cómo se determina si Notepad responde. Si el valor de la propiedad Response es true, llame al método CloseMainWindow para cerrar la aplicación. Si el valor de la propiedad Response es false, se llama al método 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 ejecución. El valor de la propiedad devuelve true si el proceso está cerrado y false si continúa en ejecución.

Nota Este valor sólo se devuelve para procesos iniciados por un componente Process.

El componente Process no necesita cerrar el proceso asociado para obtener la propiedad HasExited. La información administrativa, tal como las propiedades HasExited y ExitTime, se almacena independientemente de cómo se haya cerrado el proceso asociado. La información se almacena aunque el usuario seleccione el comando Cerrar de la interfaz para cerrar el proceso. Esta información es útil si desea asegurarse de que todos los procesos iniciados por los componentes Process se cierran al salir de una aplicación. 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 cómo 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 aplicación.

if (!notepad.HasExited) { notepad.CloseMainWindow(); }

Page 6: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 6 de 22

También podríamos querer que se ejecutase algún código 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 ejecución Cuando se trabaja con procesos en un sistema, puede ser necesario ver todos los procesos que se están ejecutando en un momento dado. Por ejemplo, si desea crear una aplicación que proporcione la funcionalidad de detener procesos, deberá ver en primer lugar qué procesos se están 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 están en ejecución

1. Declare una matriz vacía del tipo Process. 2. Llene la matriz vacía con el valor devuelto por el método 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 cómo se llama al método 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 método WaitForInputIdle antes de hacer que el proceso de destino ejecute una acción. El método WaitForInputIdle indica a un componente Process que espere a que el proceso asociado entre en estado de inactividad. Este método es útil, por ejemplo, cuando la aplicación espera a que un proceso termine de crear la ventana principal antes de comunicarse con ella. El método WaitForInputIdle sólo funciona con procesos que tengan una interfaz de usuario. Para esperar a que un proceso complete una acción

Page 7: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 7 de 22

1. Asocie una instancia de un componente Process al proceso que desee iniciar. Para

obtener más información, vea Iniciar procesos. 2. Llame al método Start para iniciar el proceso. 3. Llame al método 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 número

especificado de milisegundos a que el proceso asociado entre en estado de inactividad. El ejemplo siguiente muestra cómo se llama al método WaitForInputIdle para esperar a que termine de cargarse Notepad antes de asignar la propiedad de módulos a una matriz vacía.

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

Recuperar Datos de Procesos Podemos utilizar un componente Process para recuperar información sobre las propiedades de un proceso. Así podemos obtener información, 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 estadísticas de memoria de un proceso, el componente Process proporciona seis propiedades de uso de memoria con acceso en tiempo de ejecución. Cada propiedad proporciona una estadística diferente de asignación 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 método Refresh. Para leer la propiedad de uso de memoria que desee, haga referencia a la propiedad en cuestión. Propiedad Valores devueltos WorkingSet64 Obtiene el tamaño de la memoria física que se

ha asignado al proceso asociado. PeakWorkingSet64 Obtiene el tamaño máximo de la memoria

física que utiliza el proceso asociado. VirtualMemorySize64 Obtiene el tamaño de la memoria virtual que

se ha asignado al proceso. PeakVirtualMemorySize64 Obtiene el tamaño máximo de la memoria

virtual que utiliza el proceso asociado. PrivateMemorySize64 Número de bytes asignados al proceso

asociado que no se pueden compartir con otros procesos.

Page 8: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 8 de 22

PeakPagedMemorySize64 Obtiene el tamaño de memoria máximo en el

archivo de paginación de la memoria virtual que utiliza el proceso asociado.

PagedSystemMemorySize64 Obtiene el tamaño de la memoria paginable del sistema que se ha asignado para el proceso asociado.

PagedMemorySize64 Obtiene el tamaño de la memoria paginada asignada para el proceso asociado.

NonpagedSystemMemorySize64 Obtiene el tamaño de la memoria no paginada del sistema que se ha asignado para el proceso asociado.

El ejemplo siguiente muestra cómo 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 continuación 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 colección del tipo ProcessModules, que incluye todas las bibliotecas cargadas para el proceso de destino. A continuación, puede iterar en la colección 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 vacía de tipo ProcessModules para que contenga la colección de

módulos.

3. Asigne la propiedad Modules a la variable ProcessModules. De este modo se llenará la matriz ProcessModules con los módulos procedentes del módulo de destino.

4. Itere en la matriz ProcessModules utilizando la propiedad Array(Index) para ver y

administrar bibliotecas individuales. El ejemplo siguiente muestra cómo se devuelven todas las bibliotecas cargadas para Microsoft Word y se pasa a continuación 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;

Page 9: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 9 de 22

// 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 colección del tipo ProcessThread que representa los subprocesos del sistema operativo que están en ejecución en el proceso. A continuación, 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 colección vacía

de tipo ProcessThread.

3. Itere en el índice de la matriz para ver las propiedades de cada subproceso individual. El ejemplo siguiente muestra cómo se lee la propiedad Threads de Notepad y se asigna el valor a una matriz vacía. El valor Thread.BasePriority del primer subproceso de la matriz ProcessThread se lee a continuación 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 colección de hilos. threads = notepads[0].Threads; //Lee la propiedad BasePriority. This.Textbox1.Text = threads[0].BasePriority.ToString()

Administración de Hebras o Hilos El ámbito System.Threading proporciona en la plataforma .NET las clases e interfaces para escribir código 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 función principal y, si no se crean más, será un programa de hebra simple. Si el proceso crea más 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

Page 10: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 10 de 22

hebra actual y cargar en memoria el contexto de la hebra que se va a cargar a continuación. Este proceso se llama conmutación de contexto y añade 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 imágenes, por lo tanto, quiere mostrar cada una de ellas durante un corto espacio de tiempo. ¿Cómo va a integrar esto en el resto de la aplicación?. Sería estupendo si pudiera hacer un bucle que muestre las imágenes mientras se ejecuta el resto del programa. La ejecución del código que muestra la imagen en una hebra separada permite hacer esto. Otro ejemplo es la impresión en segundo plano: si su aplicación quiere imprimir, y no quiere que el usuario tenga que esperar mientras se envían los datos a la impresora. En segundo lugar, cuando esta realizando más de una tarea a la vez. Suponga que tiene un programa de procesado de imágenes que toma una imagen y realiza alguna operación con ella. Podría 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 pérdidas de tiempo en máquinas multiprocesador, pero sería bastante útil en máquinas 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 más de un cliente, por lo que, ¿cómo manejará al primer cliente mientras también espera al segundo cliente?. Es difícil por el hecho de que esperar en un puerto es una operación de bloqueo, así que el servidor no puede dejarlo y hacer otra cosa fácilmente. La respuesta es utilizar una hebra separada para manejar cada cliente. Cuando un cliente se conecta, el servidor inicia una hebra para manejar la sesión con el cliente y, a continuación, realiza el bucle y espera al próximo cliente.

La clase Thread La clase System.Threading.Thread representa una hebra del sistema operativo. Algunas de las propiedades y métodos más utilizados de esta clase son: Propiedad Descripción CurrentPrincipal Obtiene o pone la seguridad principal actual de la

hebra. CurrentThread Obtiene una referencia a la hebra que se esta

ejecutando. IsAlive Devuelve verdadero si la hebra ha comenzado y no

ha muerto. IsBackground Es verdadero si esta hebra es una hebra de segundo

plano. Name Obtiene o pone el nombre de esta hebra. Priority Obtiene o pone la prioridad para esta hebra.

Page 11: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 11 de 22

ThreadState Obtiene el estado de esta hebra. Método Descripción Abort Elimina la hebra. Interrupt Interrumpe una hebra que esta en el estado WaitSleepJoin. Join Espera a que una hebra termine. Resume Reanuda una hebra suspendida. Sleep Envía una hebra a dormir durante un periodo de tiempo. Start Inicia una hebra. Suspend Suspende una hebra.

El control de las hebras Una vez que ha creado el objeto Thread y ha pasado la dirección de una función a ejecutar, llame a su método Start() para comenzar la ejecución. La función hebra se ejecuta y, cuando vuelve, la hebra finaliza su ejecución. La hebra del sistema operativo entonces muere, incluso aunque el objeto Thread aún 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 método Abort(). Tenga cuidado cuando llame a este método, pues dependiendo de la situación podría producir datos corruptos, por ejemplo si estuviera en mitad de una actualización o escribiendo un archivo. Los métodos Suspend() y Resume() se pueden utilizar para parar temporalmente la ejecución de una hebra y a continuación reiniciarla de nuevo. Con estos métodos puede surgir el mismo problema que con el método Abort(). Sleep() se utiliza para poner una hebra a dormir durante un periodo de tiempo, normalmente especificado como un número de milisegundos. Este es un método 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 máquina.

Estado y prioridad de las hebras La enumeración ThreadState, describe los posibles estados que puede tener una hebra: Elemento Descripción Aborted La hebra se ha abortado y ahora esta muerta. AbortRequested La hebra se esta pidiendo para abortar. Background La hebra se esta ejecutando como una hebra en segundo plano. Running La hebra se esta ejecutando. Stopped La hebra esta parada (el valor es solo para uso interno). StopRequested La hebra se esta pidiendo para parar (el valor es solo para uso

interno). Suspended La hebra se ha suspendido. SuspendRequested La hebra se esta pidiendo para suspenderse. Unstarted La hebra aún no ha comenzado. WaitSleepJoin La hebra esta bloqueada en una llamada a Wait(), Sleep() o

Join().

Page 12: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 12 de 22

Antes de llamar al método Start(), una hebra esta Unstarted y, a continuación, pasa al estado Running. El método 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 todavía 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 aplicación, porque automáticamente se cortarán 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 enumeración ThreadPriority, cuyos valores son: Elemento Descripción Hightest La hebra tiene la prioridad más alta. AboveNormal La hebra tiene una prioridad mayor que la normal. Normal. La hebra tiene una prioridad normal. BelowNormal La hebra tiene una prioridad más baja que la normal. Lowest La hebra tiene la prioridad más 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 métodos Start(), Supsend() y Resume() respectivamente. Además 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 ejecución y el código del programa, pero el código fuente también lo podéis descargar de la página web del instituto.

Page 13: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 13 de 22

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; namespace Hebras2 { /// <summary> /// Descripción 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 diseñador 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; //Declaración de hebras Thread Hilo1,MiraHilo,Hilo2; private System.Windows.Forms. Label lbContador; private System.Windows.Forms. Button btnIniciar2; private System.Windows.Forms. Button btnSuspender2; private System.Windows.Forms. Button btnReanudar2; private System.Windows.Forms. Button btnParar2; private System.Windows.Forms. CheckBox checkBox1; private System.Windows.Forms. Label lbEstado2; //Delegados para acceder a los controles desde las hebras delegate void RellamadaPonerLabel ( string cadena); RellamadaPonerLabel pl;

Page 14: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 14 de 22

delegate void PonerEstados ( string c1, string c2); PonerEstados p2; public Form1() { // // Necesario para admitir el Diseñador de Windows F orms // InitializeComponent(); //Inicialización de delegados pl = new RellamadaPonerLabel (PonerLabel); p2 = new PonerEstados (PonerEstado); //Inicialización 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 estén utilizando. /// </summary> protected override void Dispose( bool disposing ) { if ( disposing ) { if (components != null ) { components.Dispose(); } } base .Dispose( disposing ); } #region Código generado por el Diseñador de Windows Forms /// <summary> /// Método necesario para admitir el Diseñador. No se puede modificar /// el contenido del método con el editor de código. /// </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 ();

Page 15: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 15 de 22

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); //

Page 16: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 16 de 22

// 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" ;

Page 17: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 17 de 22

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 aplicación. /// </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\VS2 005ImageLibrary\bitmaps\misc\input.bmp" ); Thread .Sleep(1000);

Page 18: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 18 de 22

this .pctImagen.Image = Image .FromFile( @"C:\Archivos de programa\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary\VS2 005ImageLibrary\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() });

Page 19: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 19 de 22

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

En este ejemplo además de ver la utilización 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 método Invoke. Más aclaraciones sobre este tema en clase. Por último decir sobre hebras que si no necesitamos para nuestra aplicación demasiado control sobre la hebra, la mejor solución es utilizar hebras administradas mediante la clase ThreadPool, estos subprocesos, con un máximo predeterminado de 25 por proceso, son gestionados por el sistema. Y a partir de la versión 2.0 tenemos la clase BackGroundWorker para lanzar subprocesos independientes y poder gestionar cuando lanzar el proceso y seguir su evolución mediante los métodos adecuados.

Sincronización de hebras Puede que necesite sincronizar hebras por dos razones: debido al uso de los recursos compartidos y por la temporización. Cada hebra tiene su propia pila y conjunto de registros, lo que significa que cada función 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 corrupción del dato. Veámoslo en la figura.

HEBRA A Establece el valor de la variable global X a 3 Llamada a la función Func(X)

HEBRA B Establece el valor de la variable global X a 4

Conmutación de contexto de la hebra A a la hebra B

Conmutación de contexto de la hebra B a la hebra A

Page 20: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 20 de 22

La hebra A establece el valor de la variable global X a 3. En este punto, sucede una conmutación 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 difíciles de ver porque suelen depender del tiempo. Si la próxima vez que ejecute el programa, la conmutación 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 semáforos para obtener el uso exclusivo de un recurso.

Las clases de sincronización El ámbito System.Threading contiene varias clases que ayudan a la sincronización de hebras. La clase Interlocked contiene cuatro métodos de hebra segura compartidos para realizar operaciones con variables. Cuatro de estos métodos son atómicos, 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 profundización en el uso de esta clase y se exigirá un ejemplo de la misma en clase. Veamos otras clases de sincronización como Mutex y Monitor.

La clase Mutex Mutex proporciona un mecanismo de sincronización 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 bloquearán hasta que la hebra haya finalizado y lo libere. El siguiente código muestra el uso de un mutex:

using System.Threading; //El mutex se usa para sincronizar dos hebras Mutex mtx = new Mutex(); //El método AcquireData se ejecuta en una hebra public void AcquireData() { //Intenta obtener el Mutex mtx.WaitOne(); //Código para adquirir los datos //Libera el Mutex mtx.ReleaseMutex()

Page 21: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 21 de 22

} //El método UseData se ejecuta en una segunda hebra public void UseData() { //Intenta obtener el Mutex mtx.WaitOne(); //Código para utilizar los datos //Libera el Mutex mtx.ReleaseMutex() }

El código 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 función, a continuación, 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 algún 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 algún 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 están bloqueadas e incapaces de avanzar.

La clase Monitor Los objetos Monitor exponen la capacidad de sincronizar el acceso a una región de código mediante la obtención y liberación de un bloqueo en un objeto en particular con los métodos Monitor.Enter, Monitor.TryEnter y Monitor.Exit. Una vez que disponga de un bloqueo en una región de código, puede utilizar los métodos 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 están disponibles para añadir 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 ¿cómo puede estar seguro que las funciones de búsqueda o impresión no se están modificando por las funciones de adición o eliminación de la lista a la misma vez?. Una solución es usar un monitor, veamos el código para la función de añadir:

//El método Add se puede ejecutar en una hebra public void Add(Object o) { //monitorizamos el arralist Monitor.Enter(myArrayList); //Utilizamos la lista

Page 22: Proceso s

Procesos e Hilos de Windows en C# Antonio Illana Vílchez

Página 22 de 22

myArrayList.Add(o); //Libera el monitor

Monitor.Exit(myArrayList); }

La clase Semaphore Esta nueva clase de sincronización, que aparece en la versión ASP.NET 2.0, limita el número de subprocesos que pueden tener acceso a un recurso o grupo de recursos simultáneamente. Para entrar en el semáforo los subprocesos llaman al método WaitOne y para liberarlo al método Release. El funcionamiento es el siguiente, cuando se crea un semáforo mediante el constructor Semaphore, se inicializa un contador que indica el número máximo de subprocesos que pueden coger el semáforo. Cada vez que se ejecuta el método WaitOne se decrementa en uno dicho valor, si este valor es cero el subproceso debe esperar a que un proceso deje libre el semáforo con el método Release . Para más detalles miren ustedes la ayuda donde pueden ver un ejemplo.