Comment : créer et terminer des threads (Guide de programmation C#)

Mise à jour : novembre 2007

Cet exemple montre comment il est possible de créer un thread auxiliaire ou de travail et de l'utiliser pour effectuer le traitement en parallèle avec le thread principal. Il est également expliqué comment faire en sorte qu'un thread en attende un autre et comment terminer un thread correctement. Pour obtenir des informations générales sur le multithreading, consultez Threading managé et Utilisation de threads (Guide de programmation C#).

L'exemple crée une classe nommée Worker qui contient la méthode, DoWork, que le thread de travail exécutera. Il s'agit essentiellement de la fonction Main pour le thread de travail. Le thread de travail commencera son exécution en appelant cette méthode, et se terminera automatiquement lors du retour de cette méthode. La méthode DoWork se présente comme suit :

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

La classe Worker contient une méthode supplémentaire qui est utilisée pour indiquer à DoWork qu'elle doit retourner. Cette méthode est nommée RequestStop et se présente comme suit :

public void RequestStop()
{
    _shouldStop = true;
}

La méthode RequestStop assigne simplement la donnée membre _shouldStop à la valeur true. Cette donnée membre étant vérifiée par la méthode DoWork, cela a l'effet indirect de causer le retour de DoWork, mettant ainsi fin au thread de travail. Toutefois, notez que DoWork et RequestStop seront exécutées par des threads différents. DoWork est exécutée par le thread de travail et RequestStop est exécutée par le thread principal. Par conséquent, la donnée membre _shouldStop est déclarée volatile, comme suit :

private volatile bool _shouldStop;

Le mot clé volatile alerte le compilateur que plusieurs threads accéderont à la donnée membre _shouldStop, et par conséquent, qu'il ne doit pas émettre d'hypothèses d'optimisation à propos de l'état de ce membre. Pour plus d'informations, consultez volatile (Référence C#).

L'utilisation de volatile avec la donnée membre _shouldStop vous permet d'accéder sans risque à ce membre à partir de plusieurs threads sans utiliser de techniques de synchronisation de threads formelles, mais uniquement parce que _shouldStop est une valeur bool. Cela signifie que seules des opérations atomiques sont nécessaires pour modifier _shouldStop. Toutefois, si cette donnée membre était une classe, un struct ou un tableau, son accès à partir de plusieurs threads entraînerait vraisemblablement une altération intermittente des données. Prenons l'exemple d'un thread qui modifie les valeurs d'un tableau. Windows interrompt régulièrement des threads pour permettre l'exécution d'autres threads. Par conséquent, ce thread pourrait être arrêté après l'assignation de certains éléments de tableau mais avant l'assignation d'autres. Le tableau ayant maintenant un état que le programmeur n'avait pas prévu, un autre thread qui lit ce tableau risque d'échouer.

Avant la création du thread de travail, la fonction Main crée un objet Worker et une instance de Thread. L'objet de thread est configuré pour utiliser la méthode Worker.DoWork comme un point d'entrée en passant une référence à cette méthode au constructeur Thread, comme suit :

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

À ce stade, même si l'objet de thread de travail existe et est configuré, le véritable thread de travail n'a pas encore été créé. Ceci ne se produit que lorsque Main appelle la méthode Start :

workerThread.Start();

À ce stade, le système initialise l'exécution du thread de travail, mais il le fait de façon asynchrone au thread principal. Cela signifie que la fonction Main continue à exécuter le code immédiatement pendant que le thread de travail subit l'initialisation en simultanéité. Pour s'assurer que la fonction Main ne tente pas de terminer le thread de travail avant qu'il ait une chance de s'exécuter, la fonction Main effectue une boucle jusqu'à ce que la propriété IsAlive de l'objet de thread de travail prenne la valeur true :

while (!workerThread.IsAlive);

Ensuite, le thread principal est interrompu brièvement avec un appel à Sleep. Cela garantit que la fonction DoWork du thread de travail exécutera la boucle à l'intérieur de la méthode DoWork pendant plusieurs itérations avant que la fonction Main exécute d'autres commandes :

Thread.Sleep(1);

Après qu'une milliseconde se soit écoulée, Main signale à l'objet de thread de travail qu'il doit se terminer au moyen de la méthode Worker.RequestStop introduite précédemment :

workerObject.RequestStop();

Il également possible de terminer un thread d'un autre thread à l'aide d'un appel à Abort. Cela entraîne la fin forcée du thread affecté, même s'il n'a pas terminé sa tâche, et ne permet pas le nettoyage des ressources. La technique illustrée dans cet exemple est la privilégiée.

Enfin, la fonction Main appelle la méthode Join sur l'objet de thread de travail. Cette méthode fait en sorte que le thread actuel se bloque, ou attende, jusqu'à ce que le thread que l'objet représente se termine. Par conséquent, Join ne retournera pas tant que le thread de travail ne retourne pas, se terminant de la façon suivante :

workerThread.Join();

À ce stade, seul le thread principal exécutant Main existe. Il affiche un dernier message, puis retourne, en mettant également fin au thread principal.

Voici l'exemple complet.

Exemple

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

Voici la sortie :

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

Voir aussi

Tâches

Threads, exemple

Concepts

Guide de programmation C#

Référence

Thread (Guide de programmation C#)

Utilisation de threads (Guide de programmation C#)

Thread

volatile (Référence C#)

Mutex

Monitor

Start

IsAlive

Sleep

Join

Abort

Autres ressources

Threading managé

Exemples de thread