Compartir a través de


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

Actualización: noviembre 2007

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 básica sobre el multiprocesamiento, vea Subprocesamiento administrado y Utilizar el subprocesamiento (Guía de programación de C#).

El ejemplo crea una clase denominada Worker, la cual contiene el método DoWork que será ejecutado por el subproceso de trabajo. É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:

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 presenta el siguiente aspecto:

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 hacer regresar a DoWork, 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:

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 desde varios subprocesos probablemente produciría daños intermitentes en los datos. Considere un subproceso que cambia los valores en una matriz. Windows interrumpe regularmente los subprocesos para permitir que se ejecuten otros subprocesos. Por consiguiente, este subproceso podría resultar detenido después de asignar algunos elementos de la matriz y antes de asignar otros. Puesto que la matriz tiene ahora un estado que el programador nunca había previsto, otro subproceso que lea la matriz puede 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:

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

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

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 intente 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:

while (!workerThread.IsAlive);

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

Thread.Sleep(1);

Después de transcurrido un milisegundo, Main señaliza al objeto de subproceso de trabajo que debería finalizar utilizando el método Worker.RequestStop que se presentó anteriormente:

workerObject.RequestStop();

También es posible finalizar un subproceso desde otro subproceso mediante una llamada a Abort. Este método obliga a la finalización del subproceso afectado aun cuando éste no haya completado su tarea, y no proporciona ninguna oportunidad para la liberación 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:

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

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.");
    }
}

Éste es el resultado:

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

Tareas

Ejemplo de subprocesamiento

Conceptos

Guía de programación de C#

Referencia

Subprocesamiento (Guía de programación de C#)

Utilizar el subprocesamiento (Guía de programación de C#)

Thread

volatile (Referencia de C#)

Mutex

Monitor

Start

IsAlive

Sleep

Join

Abort

Otros recursos

Subprocesamiento administrado

Ejemplos de subprocesamiento