Guía de programación de C#
Cómo: Crear y terminar subprocesos (Guía de programación de C#)

En este ejemplo se muestra cómo crear un subproceso auxiliar o de trabajo, y utilizarlo para realizar el procesamiento en paralelo con el subproceso primario. También se muestra cómo hacer que un subproceso espere a que termine la ejecución de otro y, a continuación, finalice correctamente. Para obtener información adicional sobre el multiprocesamiento, vea Subprocesamiento administrado y Utilizar el subprocesamiento (Guía de programación de C#).

El ejemplo crea una clase denominada Worker que contiene el método que el subproceso de trabajo ejecutará, denominado DoWork. Ésta es esencialmente la función Main para el subproceso de trabajo. El subproceso de trabajo comenzará la ejecución llamando a este método y finalizará automáticamente cuando el método devuelva un resultado. El método DoWork tiene la apariencia siguiente:

C#
public void DoWork()
{
    while (!_shouldStop)
    {
        Console.WriteLine("worker thread: working...");
    }
    Console.WriteLine("worker thread: terminating gracefully.");
}

La clase Worker contiene un método adicional que se utiliza para indicar a DoWork que debe devolver un resultado. Este método se denomina RequestStop y tiene la apariencia siguiente:

C#
public void RequestStop()
{
    _shouldStop = true;
}

El método RequestStop asigna simplemente el miembro de datos _shouldStop a true. Como el método DoWork comprueba el miembro de datos, esto tiene el efecto indirecto de que DoWork devuelva un resultado, con lo que termina el subproceso de trabajo. Sin embargo, es importante tener en cuenta que DoWork y RequestStop son ejecutados por subprocesos diferentes. El subproceso de trabajo ejecuta DoWork y el subproceso primario ejecuta RequestStop, por lo que el miembro de datos _shouldStop se declara volatile, de la forma siguiente:

C#
private volatile bool _shouldStop;

La palabra clave volatile alerta al compilador de que varios subprocesos tendrán acceso al miembro de datos _shouldStop y, en consecuencia, no debe asumir que el estado de optimización del miembro será óptimo. Para obtener más información, vea volatile (Referencia de C#).

El uso de volatile con el miembro de datos _shouldStop permite el acceso seguro de varios subprocesos a este miembro sin utilizar técnicas formales de sincronización de subprocesos, pero sólo porque _shouldStop es un valor de tipo bool. Esto significa que sólo es necesario utilizar operaciones atómicas únicas para modificar _shouldStop. Sin embargo, si este miembro de datos fuera una clase, estructura o matriz, el acceso a él de varios subprocesos probablemente produciría daños intermitentes en los datos. Considere un subproceso que cambia los valores en una matriz. Windows interrumpe con regularidad los subprocesos para permitir a otros subprocesos que se ejecuten, de modo que este subproceso se detendría después de asignar ciertos elementos de la matriz pero antes de asignar otros. Esto significa que la matriz tiene ahora un estado que el programador nunca previó y, como resultado, cuando otro subproceso lea la matriz se producirá un error.

Antes de crear realmente el subproceso de trabajo, la función Main crea un objeto Worker y una instancia de Thread. El objeto de subproceso se configura para utilizar el método Worker.DoWork como punto de entrada mediante el paso de una referencia a este método al constructor Thread, de la forma siguiente:

C#
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);

En este momento, aunque el objeto de subproceso de trabajo existe y está configurado, no se ha creado el subproceso de trabajo real todavía. Esto no ocurre hasta que Main llamada al método Start:

C#
workerThread.Start();

Entonces, el sistema inicia la ejecución del subproceso de trabajo, pero lo hace de forma asincrónica al subproceso primario. Esto significa que la función Main continúa ejecutando el código inmediatamente, mientras el subproceso de trabajo realiza simultáneamente la inicialización. Para garantizar que la función Main no intenta terminar el subproceso de trabajo antes de que pueda ejecutarse, la función Main entra en un bucle hasta que la propiedad IsAlive del objeto de subproceso de trabajo se establece en true:

C#
while (!workerThread.IsAlive);

A continuación, el subproceso primario se detiene brevemente con una llamada a Sleep. Esto asegura que la función DoWork del subproceso de trabajo ejecutará el bucle dentro del método DoWork durante unos pocos recorridos antes de que la función Main ejecute más comandos:

C#
Thread.Sleep(1);

Después de que transcurre un milisegundo, Main señala al objeto de subproceso de trabajo que debería finalizar utilizando el método Worker.RequestStop que se introdujo previamente:

C#
workerObject.RequestStop();

También es posible terminar un subproceso desde otro con una llamada a Abort, pero esto termina a la fuerza el subproceso afectado sin comprobar si ha completado su tarea y no proporciona ninguna oportunidad para la limpieza de recursos. Es preferible la técnica mostrada en este ejemplo.

Por último, la función Main llama al método Join en el objeto de subproceso de trabajo. Este método hace que el subproceso actual se bloquee, o espere, hasta que el subproceso que el objeto representa finalice. Por consiguiente, Join no devolverá ningún resultado hasta que el subproceso de trabajo vuelva, con lo cual finaliza:

C#
workerThread.Join();

En este momento, sólo existe el subproceso primario que ejecuta Main. Muestra un mensaje final y, después, vuelve y finaliza también el subproceso primario.

A continuación se muestra el ejemplo completo:

Ejemplo

C#
using System;
using System.Threading;

public class Worker
{
    // This method will be called when the thread is started.
    public void DoWork()
    {
        while (!_shouldStop)
        {
            Console.WriteLine("worker thread: working...");
        }
        Console.WriteLine("worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    // Volatile is used as hint to the compiler that this data
    // member will be accessed by multiple threads.
    private volatile bool _shouldStop;
}

public class WorkerThreadExample
{
    static void Main()
    {
        // Create the thread object. This does not start the thread.
        Worker workerObject = new Worker();
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();
        Console.WriteLine("main thread: Starting worker thread...");

        // Loop until worker thread activates.
        while (!workerThread.IsAlive);

        // Put the main thread to sleep for 1 millisecond to
        // allow the worker thread to do some work:
        Thread.Sleep(1);

        // Request that the worker thread stop itself:
        workerObject.RequestStop();

        // Use the Join method to block the current thread 
        // until the object's thread terminates.
        workerThread.Join();
        Console.WriteLine("main thread: Worker thread has terminated.");
    }
}

Resultados del ejemplo

main thread: starting worker thread...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: terminating gracefully...
main thread: worker thread has terminated

Vea también

Etiquetas :


Community Content

Juan Carlos Ruiz [BogotaDotNet.org]
C# – la palabra clave volatile, explicación y ejemplos
El ejemplo que reposa en esta documentacion de msdn no funciona par demostrar la funcionalidad de volatile.


La palabra clave volatile es una de esas palabras clave muy pocas veces comprendidas, la documentación presente en msdn permite concluir que hay que utilizarla siempre que se manejen hilos, pero esto no siempre es así. Sin embargo lograr identificar que es lo que hace realmente esta palabra clave es una labor complicada así que dedicaré este artículo a explorar esta funcionalidad y a crear un ejemplo práctico que permita entender su verdadera naturaleza.

En msdn encontramos la siguiente definición de la palabra clave volatile:


La palabra clave volatile indica que varios subprocesos que se ejecutan a la vez pueden modificar un campo. Los campos que se declaran como volatile no están sujetos a optimizaciones del compilador que suponen el acceso por un subproceso único. Esto garantiza que el valor más actualizado está en todo momento presente en el campo

Debemos resaltar dos aspectos importantes de ese texto:

  1. Se menciona que los campos volatile no son susceptibles de optimizaciones por parte del compilador. Cuales optimizaciones?
  2. Dice que esto garantiza que el valor más actualizado siempre esta presente en el campo. No se supone que esto es así siempre?

En este artículo se revisan estas dos preguntas y por medio de ellas se deducen un conjunto de ejemplos reales y comprobables de la funcionalidad de volatile.

Optimizaciones del compilador

Siempre que compilamos un programa hecho con C# el compilador se encarga de convertir ese código C# en código de lenguaje IL, bueno realmente en OpCodes de IL. Esto es así de sencillo, pero resulta que cuando compilamos nuestro código en la configuración release o más específicamente cuando se marca la casilla de Optimizar código en el proyecto el compilador realiza una revisión general del código par determinar que cosas puede hacer funcionar de una manera mejor a la que codificó el programador inicialmente o incluso como puede cambiar las cosas en el ejecutable que no están en manos del programador ni del propio lenguaje para que a la hora de ejecutarse el programa sea más eficiente.

Articulo completo:

http://juank.black-byte.com/c-explicacion-ejemplo-volatile/

Etiquetas : autobombo

Page view tracker