Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
Pgina 2 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 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
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(); }
Pgina 4 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 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(); }
Pgina 5 de 22
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"); }
Pgina 6 de 22
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();
Pgina 7 de 22
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);
Pgina 8 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 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()
Pgina 9 de 22
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.
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
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.
Pgina 11 de 22
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
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
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
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
// 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
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
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
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
Pgina 19 de 22
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.
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
} //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
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