CodeGym /Cours /C# SELF /Cycle de vie d'un thread et gestion

Cycle de vie d'un thread et gestion

C# SELF
Niveau 55, Leçon 2
Disponible

1. Introduction

Imagine que le thread soit un collègue infatigable à qui tu confies une tâche. Le collègue peut dormir (pas encore commencé), bosser à fond (execution de ta méthode), attendre qu'on lui donne quelque chose (idle), ou finir son boulot (terminé).

En C# (et plus généralement en .NET) le cycle de vie d'un thread contient plusieurs états :

  • Unstarted — le thread est créé mais pas encore démarré.
  • Running — le thread s'exécute.
  • WaitSleepJoin — le thread est temporairement inactif (par ex. il attend un signal ou il "dort").
  • Stopped — le thread a fini la tâche et s'est terminé.

On peut représenter ce cycle de façon visuelle avec ce schéma :

stateDiagram-v2
    [*] --> Unstarted
    Unstarted --> Running: Start()
    Running --> WaitSleepJoin: Wait/Sleep/Join
    WaitSleepJoin --> Running: Signal reçu/Temps écoulé
    Running --> Stopped: Méthode terminée
    WaitSleepJoin --> Stopped: Méthode terminée
    Stopped --> [*]

Tout commence avec la création d'un objet Thread, mais tant que tu n'appelles pas Start(), le thread "dort" en Unstarted. Après Start() — la fête commence, le thread passe en Running. Si le thread appelle Thread.Sleep ou attend quelque chose (par ex. Monitor.Wait), il va dans l'état d'attente. Dès que la méthode passée au thread se termine, le thread meurt, il n'est pas "ressuscité". Bref, ticket aller simple.

2. Pratique : Cycle de vie d'un thread simple

Regardons l'exemple classique :

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // On crée le thread — on planifie juste le travail pour l'instant
        Thread worker = new Thread(DoWork);

        Console.WriteLine($"État du thread après création : {worker.ThreadState}");

        // On démarre le thread
        worker.Start();
        Console.WriteLine($"État du thread après démarrage : {worker.ThreadState}");

        // On laisse le thread principal dormir un peu pour que le worker ait le temps de bosser
        Thread.Sleep(100);

        Console.WriteLine($"État du thread (plus tard) : {worker.ThreadState}");

        // On attend que worker se termine (on se joint)
        worker.Join();

        Console.WriteLine($"État du thread après terminaison : {worker.ThreadState}");
        Console.WriteLine("Le thread principal est terminé");
    }

    static void DoWork()
    {
        Console.WriteLine("Le thread de travail a commencé !");
        Thread.Sleep(500);
        Console.WriteLine("Le thread de travail a terminé !");
    }
}

Que affiche le programme ?

  1. Après la création du thread — l'état sera Unstarted.
  2. Après le démarrage — généralement immédiatement Running (mais il peut aussi être Running | Background).
  3. Pendant l'exécution — l'état peut être Running, ou WaitSleepJoin si le thread "dort".
  4. Après la fin de la méthode — l'état devient Stopped.

Ce code est un excellent outil pour comprendre dans quel état peut se trouver ton thread. Tu peux jouer avec les délais pour voir comment l'état change.

3. Gestion d'un thread : méthodes principales

Démarrage : Start()

C'est évident, mais on le rappelle : tu crées un thread — appelle Start(). Et tu ne peux l'appeler qu'une seule fois : tenter d'appeler Start() une deuxième fois lèvera une exception ThreadStateException.

Thread t = new Thread(MyMethod);
t.Start();   // OK
t.Start();   // Erreur !

Attendre la fin : Join()

Parfois tu dois attendre qu'un thread finisse avant de continuer. Pour ça il y a Join().

Thread t = new Thread(MyMethod);
t.Start();
t.Join(); // Bloque le thread courant jusqu'à la fin de t

Si tu as plusieurs threads, tu peux appeler Join() sur chacun — le thread principal attendra que tous les "bosseurs" aient fini.

Variantes : il existe la surcharge Join(int millisecondsTimeout), qui n'attend qu'un temps donné, puis continue.

// On attend pas plus de 2 secondes
if (t.Join(2000))
    Console.WriteLine("Le thread s'est terminé à temps");
else
    Console.WriteLine("On en a marre d'attendre...");

Arrêt forcé : pourquoi c'est une mauvaise idée

Dans les vieilles versions de .NET il y avait Thread.Abort(), qui permettait de "tuer" un thread sur le vif. Aujourd'hui on le voit rarement — c'est dangereux et ça peut laisser l'app en mauvais état. La philosophie .NET : le thread doit se terminer de son plein gré. Tu ne "tues" pas le collègue — tu lui fais poliment comprendre que la journée est finie.

4. Comment "arrêter" correctement un thread

La façon la plus correcte et la plus sûre d'arrêter un thread est d'utiliser un flag d'annulation ou un indicateur que le thread vérifie périodiquement.

class Worker
{
    private volatile bool shouldStop = false;

    public void DoWork()
    {
        while (!shouldStop)
        {
            Console.WriteLine("Je bosse !");
            Thread.Sleep(300);
        }

        Console.WriteLine("Le thread termine le travail sur commande.");
    }

    public void RequestStop()
    {
        shouldStop = true;
    }
}

Usage :

Worker w = new Worker();
Thread t = new Thread(w.DoWork);
t.Start();

// On attend un peu
Thread.Sleep(1000);

// On demande au thread de s'arrêter
w.RequestStop();
t.Join(); // On attend la fin du thread

Point important : volatile

Le mot-clé volatile indique au compilateur et au processeur : "Ne mets pas ce champ en cache, prends toujours la valeur actuelle !" C'est important pour que le thread voie la mise à jour du flag d'arrêt. Sans ça (ou sans d'autres mécanismes de synchronisation) le thread peut ne jamais remarquer ta modification.

5. Passage des threads en attente et en sommeil

Parfois un thread ne fait rien pendant un moment — soit il attend, soit il dort.

Sommeil : Thread.Sleep

Quand tu veux faire reposer un thread ou ralentir l'exécution (par ex. pour ne pas saturer le CPU), utilise Thread.Sleep(milliseconds).

// Le thread dort 2 secondes
Thread.Sleep(2000);

Pendant le sommeil le thread ne fait aucun travail.

Attente / Join

Quand le thread principal attend la fin d'un fils (Join), il est "en pause". De même, si un thread attend qu'une ressource soit libérée (par ex. via des monitors ou d'autres primitives de synchronisation), il passe dans un état d'attente spécial.

6. Gestion du caractère background d'un thread

En .NET les threads peuvent être de deux types : foreground (premier plan) et background (background). La différence est simple :

  • Si le process n'a plus que des threads background, le process se termine automatiquement.
  • Le thread principal et tous les threads en foreground doivent se terminer pour que le process s'arrête.

Tu peux explicitement indiquer qu'un thread est background :

Thread t = new Thread(SomeMethod);
t.IsBackground = true; // Marqué comme background
t.Start();

Exemple pratique — démon vs thread normal

Thread t = new Thread(() =>
{
    while (true)
    {
        Console.WriteLine("Je suis un fantôme (background), tu ne peux pas m'arrêter !");
        Thread.Sleep(500);
    }
});
t.IsBackground = true; // On rend background
t.Start();

Thread.Sleep(1200);
Console.WriteLine("Le thread principal termine");
// Après la fin de Main — le process meurt, et notre thread infini disparaît aussi

Après la fin de Main — le process se termine ; les threads background sont arrêtés automatiquement.

7. Astuces utiles

Ce qu'il ne faut pas faire avec les threads

  • Ne pas "redémarrer" un thread. L'objet Thread vit une seule fois : dès que sa méthode est terminée — le thread est mort, et tenter d'appeler Start() à nouveau lèvera une erreur.
  • Ne pas arrêter de force un thread étranger avec des méthodes comme Thread.Abort() ou Thread.Suspend() — c'est obsolète et dangereux.
  • Ne pas ignorer la fin d'un thread. Si un thread travaille avec des fichiers ou des ressources, assure-toi de les libérer correctement avant la terminaison du thread.

Vérifier l'état et gérer le cycle de vie

if (t.IsAlive)
{
    Console.WriteLine("Le thread est toujours vivant");
}
else
{
    Console.WriteLine("Le thread est terminé");
}

IsAlive est true tant que le thread exécute sa méthode ; après la terminaison — false.

Cycle de vie d'un thread simple en .NET

État Comment y parvenir Que ça signifie ? Comment en sortir
Unstarted
new Thread(...)
Thread créé, non démarré Appeler Start()
Running
Start()
Le thread exécute du travail Terminer la méthode
WaitSleepJoin Sleep(), Join(), attente Thread temporairement inactif L'attente se termine
Stopped Méthode du thread terminée Le thread est "mort" Il n'en sortira pas — fin

FAQ sur la gestion du cycle de vie

Question : Peut-on tuer un thread sur commande ?
Réponse : Non et pas nécessaire ; les threads doivent eux-mêmes gérer leur fin. Utilise des flags d'annulation.

Question : Peut-on réutiliser un objet Thread ?
Réponse : Non. Crée un nouvel objet pour une nouvelle tâche.

Question : Que se passe-t-il si le thread principal se termine alors qu'un fils continue de bosser ?
Réponse : Si le thread fils est background (IsBackground == true), l'application se terminera. Sinon — le process restera vivant tant que tous les threads n'auront pas fini.

Question : Comment libérer proprement les ressources si un thread se termine à cause d'une annulation ?
Réponse : Utilise des blocs try...finally à l'intérieur de la méthode du thread pour garantir la libération des ressources dans tous les cas.

8. Erreurs typiques et comment les éviter quand on bosse avec des threads

Erreur n°1 : réutiliser le même objet Thread.
Tu ne peux pas démarrer le même objet thread plus d'une fois. Après sa terminaison il est inutilisable — tenter de le redémarrer lèvera une exception.

Erreur n°2 : mauvaise libération des ressources externes dans un thread.
Si un thread travaille avec des fichiers, du réseau ou d'autres ressources, assure-toi de bien les fermer et de les libérer. Il est recommandé d'utiliser des blocs finally ou la construction using pour éviter les fuites et les blocages.

Erreur n°3 : créer trop de threads.
Un nombre excessif de threads complique le debug et peut réduire les perf. Un thread de trop, c'est du temps perdu à traquer des bugs inattendus.

2
Mission
C# SELF, niveau 55, leçon 2
Bloqué
États principaux d'un thread
États principaux d'un thread
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION