CodeGym /Cours /C# SELF /Exceptions dans les Thread classiques

Exceptions dans les Thread classiques

C# SELF
Niveau 61, Leçon 3
Disponible

1. Introduction

Quand on bosse avec la classe Task ou des méthodes asynchrones (async/await), on peut attraper les exceptions de la manière habituelle — via try-catch autour de await ou avec ContinueWith. Les exceptions ne «se perdent» pas, elles sont renvoyées dans le flux appelant.

Mais si on crée un thread via Thread, tout devient plus compliqué. Chaque thread a son propre point d'entrée (ThreadStart) et son propre contexte d'exécution. Si une exception non gérée survient dans le thread, elle ne «retourne» pas au thread principal — elle est lancée seulement dans ce thread.

  • Dans .NET Framework : une exception non gérée dans un thread termine toute l'application.
  • Dans .NET (Core/5+) : seul ce thread va se terminer, l'application continue de tourner (ce qui peut amener des bugs cachés).

Conclusion : si vous n'attrapez pas les exceptions à l'intérieur du thread, vous les verrez probablement tout simplement pas. Donc une gestion correcte des erreurs dans les threads est nécessaire.

Fait intéressant : les exceptions qui s'échappent d'un Thread sont comme des ninjas insaisissables : elles disparaissent, et après vous vous creusez la tête pour comprendre pourquoi la logique n'a pas fonctionné.

2. Comment les exceptions fonctionnent-elles à l'intérieur d'un thread ?

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();

        // On attend la fin du thread pour voir ce qui se passe
        thread.Join();

        Console.WriteLine("Main s'est terminé normalement");
    }

    static void DoWork()
    {
        Console.WriteLine("On travaille dans un thread séparé...");
        throw new Exception("Problème ! Une erreur est survenue dans le thread.");
    }
}

Selon la plateforme (ancien .NET Framework ou moderne .NET Core/5/6/7/8/9), le comportement sera différent : soit toute l'application va crasher, soit seul le thread. Mais l'essentiel — l'exception n'arrivera pas dans le thread principal, et vous ne pourrez pas la gérer depuis l'extérieur.

Important ! Tenter d'enrouler thread.Join() dans un try-catch n'aidera pas à attraper l'exception d'un autre thread — elle «vit» et «meurt» à l'intérieur de ce thread.

3. Comment faut-il attraper les exceptions dans un Thread ?

Seulement à l'intérieur du thread — dans la fonction que vous passez au constructeur du Thread. Tout ce qui peut lancer des erreurs, entourez-le d'un try-catch.

static void DoWork()
{
    try
    {
        Console.WriteLine("On bosse...");
        throw new Exception("Encore quelque chose qui tourne mal !");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[Thread] Exception attrapée : {ex.Message}");
        // Ici on peut logger, envoyer vers l'UI/le serveur, etc.
    }
}

La gestion des exceptions dans les threads est la responsabilité du code du thread. Ne comptez pas sur le code appelant pour intercepter l'erreur automatiquement.

4. Comment savoir dans le thread principal que quelque chose s'est mal passé dans l'autre thread ?

Dans les applications réelles il est important de remonter l'information d'erreur vers le thread principal.

  • Utilisez des mécanismes thread-safe, par exemple ConcurrentQueue<Exception>, pour transmettre les exceptions depuis les threads.
  • Élevez des events/delegates depuis le thread de travail.
  • Préférez Task, qui remontera l'exception jusqu'à l'appel de await «out of the box».

Exemple : on collecte l'erreur dans un endroit spécial

using System;
using System.Threading;

class Program
{
    static Exception? threadException = null;

    static void Main()
    {
        Thread thread = new Thread(DoWork);
        thread.Start();
        thread.Join();

        if (threadException != null)
        {
            Console.WriteLine($"Dans l'autre thread une erreur est survenue : {threadException.Message}");
        }
        else
        {
            Console.WriteLine("Le thread s'est terminé sans erreurs.");
        }
    }

    static void DoWork()
    {
        try
        {
            throw new Exception("Problème dans l'autre thread !");
        }
        catch (Exception ex)
        {
            threadException = ex;
        }
    }
}

Remarque : cette approche fonctionne pour une attente synchrone (Join()). Si le thread «vit sa vie» ou si les erreurs sont nombreuses — utilisez ConcurrentQueue<Exception>, des events ou d'autres mécanismes de communication.

5. Comparaison avec Task : pourquoi la gestion des erreurs est plus simple

async Task FooAsync()
{
    throw new Exception("Erreur dans la task !");
}

try
{
    await FooAsync();
}
catch (Exception ex)
{
    Console.WriteLine($"Erreur attrapée : {ex.Message}");
}

Ici tout est transparent : l'erreur «atteint» l'endroit où vous la await-ez. Avec le Thread classique l'erreur reste à l'intérieur du thread et ne remonte pas sans actions explicites. C'est un des arguments en faveur de l'utilisation de Task et des abstractions modernes.

6. Exemple pratique

Dans les applications UI (WPF/WinForms) on utilise les threads pour ne pas bloquer l'interface. Les exceptions non gérées mènent à un «écran gris» et des freezes étranges.

Mauvais (thread sans gestion d'erreur)

Thread thread = new Thread(() =>
{
    // On réfléchit longtemps
    Thread.Sleep(5000);
    throw new Exception("Tout est perdu !"); // personne ne va attraper ça
});
thread.Start();

Bien (on attrape l'erreur et on notifie l'utilisateur)

Thread thread = new Thread(() =>
{
    try
    {
        Thread.Sleep(5000);
        throw new Exception("Quelque chose ne va pas");
    }
    catch (Exception ex)
    {
        // On peut afficher un MessageBox, logger ou transmettre à l'UI
        Console.WriteLine($"Erreur dans le thread : {ex.Message}");
    }
});
thread.Start();

7. Nuances utiles

Hook global pour les exceptions non gérées de thread

AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
    Console.WriteLine($"Erreur attrapée globalement : {((Exception)args.ExceptionObject).Message}");
};

Thread thread = new Thread(() =>
{
    throw new Exception("Exterminatus !");
});
thread.Start();

Le handler AppDomain.CurrentDomain.UnhandledException se déclenche pour les exceptions non gérées dans les threads, mais ne permet pas de «ramener à la vie» le thread ou d'empêcher la fin du process dans .NET Framework. Dans .NET (Core/5+) il logge l'erreur ; l'application peut continuer à tourner si les autres threads sont actifs.

Différences dans la gestion des exceptions — Thread vs Task

Thread
Task
Où attraper À l'intérieur du thread Dans le code appelant (await, ContinueWith, etc.)
Conséquences L'exception se perd/tue le thread (ou toute l'application dans .NET Framework) L'exception atteint l'endroit d'attente (await)
Notification vers le haut Seulement explicitement (variables, events, queues) Via await, AggregateException lors d'une attente synchrone
Logging À faire manuellement dans le code du thread Généralement dans try-catch autour de await
Contexte Indépendant du thread parent Task utilise le SynchronizationContext du code appelant (par ex. le contexte UI en WPF)

8. Erreurs typiques en travaillant avec les exceptions dans Thread

Erreur n°1 : ne pas attraper les exceptions à l'intérieur du thread.
Au final vous pouvez obtenir une terminaison implicite d'une partie de l'application, parfois du process entier, sans diagnostic clair.

Erreur n°2 : essayer d'«attraper» l'exception du thread dans le thread principal.
Ça ne marche pas : un try-catch autour de thread.Join() ou thread.Start() ne capturera pas l'erreur lancée à l'intérieur du thread.

Erreur n°3 : perdre l'information sur l'erreur.
Si le thread est tombé et que vous n'avez pas transmis l'exception explicitement (variable, queue, event), vous ne connaîtrez ni la cause ni les détails. Ça mène à des bugs «fantômes».

Erreur n°4 : absence de logging.
Loggez toujours les erreurs dans les threads, même si ça vous semble «pas grave».

2
Mission
C# SELF, niveau 61, leçon 3
Bloqué
Gestionnaire global des exceptions non gérées
Gestionnaire global des exceptions non gérées
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION