Condividi tramite


Procedura: creare e terminare thread (Guida per programmatori C#)

Aggiornamento: novembre 2007

Nell'esempio riportato di seguito viene illustrato come creare un thread ausiliario, o di lavoro, da utilizzare per eseguire l'elaborazione in parallelo con il thread primario. Vengono inoltre descritte le procedure per mettere un thread in attesa di un altro e per terminare correttamente un thread. Per informazioni complementari sul multithreading, vedere Threading gestito e Utilizzo del threading (Guida per programmatori C#).

In questo esempio viene creata una classe denominata Worker contenente il metodo DoWork che verrà eseguito dal thread di lavoro. Questa è essenzialmente la funzione Main per il thread di lavoro. Quando verrà eseguito, il thread di lavoro chiamerà questo metodo e terminerà automaticamente quando il metodo verrà restituito. Il metodo DoWork è analogo al seguente:

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

La classe Worker contiene un altro metodo utilizzato per indicare che DoWork deve essere restituito. Questo metodo, denominato RequestStop, è analogo al seguente:

public void RequestStop()
{
    _shouldStop = true;
}

Il metodo RequestStop assegna semplicemente il membro dati _shouldStop a true. Poiché questo membro dati è controllato dal metodo DoWork, si ottiene l'effetto indiretto di causare la restituzione di DoWork terminando in questo modo il thread di lavoro. È tuttavia importante tenere presente che DoWork e RequestStop verranno eseguiti da thread differenti. DoWork viene eseguito dal thread di lavoro, mentre RequestStop viene eseguito dal thread primario, quindi il membro dati _shouldStop viene dichiarato volatile, come riportato di seguito:

private volatile bool _shouldStop;

La parola chiave volatile avvisa il compilatore che più thread accederanno al membro dati _shouldStop e che pertanto non deve formulare ipotesi di ottimizzazione sullo stato di questo membro. Per ulteriori informazioni, vedere volatile (Riferimenti per C#).

L'utilizzo di volatile con il membro dati _shouldStop consente di accedere in modo sicuro a questo membro da più thread senza utilizzare le tecniche formali di sincronizzazione dei thread, ma solo perché _shouldStop è un valore bool. Questo significa che sono necessarie solo singole operazioni atomiche per modificare _shouldStop. Se tuttavia questo membro dati fosse una classe, una struttura o una matrice, l'accesso da più thread genererebbe un danneggiamento intermittente dei dati. Si consideri un thread che cambia i valori di una matrice. In Windows i thread vengono interrotti regolarmente per consentire l'esecuzione di altri thread. Pertanto, questo thread potrebbe essere interrotto dopo l'assegnazione di valori ad alcuni elementi della matrice ma prima dell'assegnazione di valori ad altri elementi. Poiché la matrice ha in questo caso uno stato non previsto dal programmatore, un altro thread che legge la stessa matrice potrebbe generare un errore.

Prima di creare il thread di lavoro, la funzione Main crea un oggetto Worker e un'istanza di Thread. L'oggetto thread viene configurato per utilizzare il metodo Worker.DoWork come punto di ingresso passando al costruttore Thread un riferimento a questo metodo, come riportato di seguito:

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

A questo punto, anche se l'oggetto thread di lavoro esiste ed è configurato, il thread di lavoro effettivo non è ancora stato creato. Ciò si verifica solo quando Main chiama il metodo Start:

workerThread.Start();

A questo punto il sistema avvia l'esecuzione del thread di lavoro, ma in modo asincrono rispetto al thread primario. La funzione Main continua infatti ad eseguire immediatamente il codice mentre il thread di lavoro viene sottoposto contemporaneamente a inizializzazione. Per evitare che Main tenti di terminare il thread di lavoro prima che venga eseguito, la funzione Main esegue un ciclo finché la proprietà IsAlive dell'oggetto thread di lavoro non viene impostata su true:

while (!workerThread.IsAlive);

Successivamente il thread primario viene interrotto brevemente con una chiamata a Sleep. In questo modo la funzione DoWork del thread di lavoro eseguirà il ciclo all'interno del metodo DoWork per alcune iterazioni prima che la funzione Main esegua altri comandi:

Thread.Sleep(1);

Trascorso un millisecondo, Main segnala all'oggetto thread di lavoro che deve terminare utilizzando il metodo Worker.RequestStop descritto in precedenza:

workerObject.RequestStop();

È inoltre possibile terminare un thread da un altro thread utilizzando una chiamata a Abort. In questo modo il thread interessato viene terminato in modo forzato anche se non ha completato l'attività e non consente la pulitura delle risorse. È preferibile utilizzare la tecnica illustrata in questo esempio.

Infine la funzione Main chiama il metodo Join sull'oggetto thread di lavoro. Tramite questo metodo il thread corrente si blocca oppure attende finché non termina il thread rappresentato dall'oggetto. Pertanto Join non verrà restituito finché non viene restituito, e quindi terminato, il thread di lavoro:

workerThread.Join();

A questo punto esiste solo il thread primario che esegue Main. Visualizza un messaggio finale e quindi viene restituito e terminato.

Di seguito è riportato l'esempio completo.

Esempio

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

L'output è il seguente:

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

Vedere anche

Attività

Esempio di threading

Concetti

Guida per programmatori C#

Riferimenti

Threading (Guida per programmatori C#)

Utilizzo del threading (Guida per programmatori C#)

Thread

volatile (Riferimenti per C#)

Mutex

Monitor

Start

IsAlive

Sleep

Join

Abort

Altre risorse

Threading gestito

Esempi di threading