Gestionnaire d'exceptions globales .NET dans l'application console

199

Question: Je souhaite définir un gestionnaire d'exceptions global pour les exceptions non gérées dans mon application console. Dans asp.net, on peut en définir un dans global.asax, et dans les applications / services Windows, on peut définir comme ci-dessous

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

Mais comment puis-je définir un gestionnaire d'exceptions global pour une application console?
currentDomain ne semble pas fonctionner (.NET 2.0)?

Éditer:

Argh, stupide erreur.
Dans VB.NET, il faut ajouter le mot clé "AddHandler" devant currentDomain, sinon on ne voit pas l'événement UnhandledException dans IntelliSense ...
C'est parce que les compilateurs VB.NET et C # traitent la gestion des événements différemment.

Stefan Steiger
la source

Réponses:

283

Non, c'est la bonne façon de procéder. Cela a fonctionné exactement comme il se doit, quelque chose à partir duquel vous pouvez travailler:

using System;

class Program {
    static void Main(string[] args) {
        System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;
        throw new Exception("Kaboom");
    }

    static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e) {
        Console.WriteLine(e.ExceptionObject.ToString());
        Console.WriteLine("Press Enter to continue");
        Console.ReadLine();
        Environment.Exit(1);
    }
}

Gardez à l'esprit que vous ne pouvez pas intercepter les exceptions de type et de chargement de fichier générées par la gigue de cette façon. Ils se produisent avant que votre méthode Main () ne démarre. Pour les attraper, il faut retarder la gigue, déplacer le code risqué dans une autre méthode et lui appliquer l'attribut [MethodImpl (MethodImplOptions.NoInlining)].

Hans Passant
la source
3
J'ai implémenté ce que vous proposez ici, mais je ne veux pas quitter l'application. Je veux juste l'enregistrer et continuer le processus (sans Console.ReadLine()ou toute autre perturbation du déroulement du programme. Mais ce que j'obtiens est l'exception de sur-relancer encore et encore, et encore.
3
@Shahrooz Jefri: Vous ne pouvez pas continuer une fois que vous obtenez une exception non gérée. La pile est foirée, et c'est le terminal. Si vous avez un serveur, ce que vous pouvez faire dans UnhandledExceptionTrapper est de redémarrer le programme avec les mêmes arguments de ligne de commande.
Stefan Steiger
6
C'est certainement le cas! Nous ne parlons pas de l'événement Application.ThreadException ici.
Hans Passant
4
Je pense que la clé pour comprendre cette réponse et les commentaires en réponse est de réaliser que ce code démontre maintenant pour détecter l'exception, mais pas "la gérer" complètement comme vous le feriez dans un bloc try / catch normal où vous avez la possibilité de continuer . Voir mon autre réponse pour plus de détails. Si vous "gérez" l'exception de cette façon, vous n'avez pas la possibilité de continuer à exécuter lorsqu'une exception se produit sur un autre thread. En ce sens, on peut dire que cette réponse ne "gère" pas complètement l'exception si par "gérer" vous voulez dire "traiter et continuer à courir".
BlueMonkMN
4
Sans oublier qu'il existe de nombreuses technologies à exécuter dans différents threads. Par exemple, la bibliothèque parallèle de tâches (TPL) a sa propre façon de détecter les exceptions non gérées. Donc, dire que cela ne fonctionne pas dans toutes les situations est un peu ridicule, il n'y a PAS de fourre-tout pour tout en C #, mais selon les technologies que vous utilisez, vous pouvez vous connecter à différents endroits.
Doug
23

Si vous avez une application à un seul thread, vous pouvez utiliser un simple try / catch dans la fonction Main, cependant, cela ne couvre pas les exceptions qui peuvent être levées en dehors de la fonction Main, sur d'autres threads, par exemple (comme indiqué dans d'autres commentaires). Ce code montre comment une exception peut entraîner la fermeture de l'application même si vous avez essayé de la gérer dans Main (notez comment le programme se termine correctement si vous appuyez sur Entrée et autorisez l'application à se fermer correctement avant que l'exception ne se produise, mais si vous la laissez s'exécuter) , il se termine malheureusement):

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Vous pouvez recevoir une notification lorsqu'un autre thread lève une exception pour effectuer un nettoyage avant la fermeture de l'application, mais pour autant que je sache, vous ne pouvez pas, à partir d'une application console, forcer l'application à continuer de fonctionner si vous ne gérez pas l'exception sur le thread à partir duquel elle est lancée sans utiliser certaines options de compatibilité obscures pour que l'application se comporte comme elle l'aurait fait avec .NET 1.x. Ce code montre comment le thread principal peut être notifié des exceptions provenant d'autres threads, mais se terminera toujours malheureusement:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   Console.WriteLine("Notified of a thread exception... application is terminating.");
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Donc, à mon avis, la façon la plus propre de le gérer dans une application console est de s'assurer que chaque thread a un gestionnaire d'exceptions au niveau racine:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   try
   {
      for (int i = 5; i >= 0; i--)
      {
         Console.Write("24/{0} =", i);
         Console.Out.Flush();
         Console.WriteLine("{0}", 24 / i);
         System.Threading.Thread.Sleep(1000);
         if (exiting) return;
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception on the other thread");
   }
}
BlueMonkMN
la source
TRY CATCH ne fonctionne pas en mode release pour des erreurs inattendues: /
Muflix
12

Vous devez également gérer les exceptions des threads:

static void Main(string[] args) {
Application.ThreadException += MYThreadHandler;
}

private void MYThreadHandler(object sender, Threading.ThreadExceptionEventArgs e)
{
    Console.WriteLine(e.Exception.StackTrace);
}

Oups, désolé pour Winforms, pour tous les threads que vous utilisez dans une application console, vous devrez les placer dans un bloc try / catch. Les threads d'arrière-plan qui rencontrent des exceptions non gérées ne provoquent pas la fin de l'application.

Glace noir
la source
1

Je viens d'hériter d'une ancienne application de console VB.NET et j'avais besoin de configurer un gestionnaire d'exceptions global. Étant donné que cette question mentionne VB.NET à quelques reprises et est étiquetée avec VB.NET, mais toutes les autres réponses ici sont en C #, j'ai pensé que j'ajouterais également la syntaxe exacte pour une application VB.NET.

Public Sub Main()
    REM Set up Global Unhandled Exception Handler.
    AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf MyUnhandledExceptionEvent

    REM Do other stuff
End Sub

Public Sub MyUnhandledExceptionEvent(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
    REM Log Exception here and do whatever else is needed
End Sub

J'ai utilisé le REMmarqueur de commentaire au lieu de la citation simple ici parce que Stack Overflow semblait gérer la syntaxe en soulignant un peu mieux avec REM.

JustSomeDude
la source
-13

Ce que vous essayez devrait fonctionner selon les documents MSDN pour .Net 2.0. Vous pouvez également essayer un try / catch en main autour de votre point d'entrée pour l'application console.

static void Main(string[] args)
{
    try
    {
        // Start Working
    }
    catch (Exception ex)
    {
        // Output/Log Exception
    }
    finally
    {
        // Clean Up If Needed
    }
}

Et maintenant, votre capture gérera tout ce qui n'est pas capturé ( dans le fil principal ). Il peut être gracieux et même redémarrer là où il était si vous le souhaitez, ou vous pouvez simplement laisser l'application mourir et enregistrer l'exception. Vous ajouterez enfin un si vous vouliez faire un nettoyage. Chaque thread nécessitera sa propre gestion des exceptions de haut niveau similaire à la principale.

Modifié pour clarifier le point sur les threads comme souligné par BlueMonkMN et montré en détail dans sa réponse.

Rodney S. Foley
la source
1
Malheureusement, des exceptions peuvent encore être levées en dehors du bloc Main (). Ce n'est pas vraiment un "fourre-tout" comme vous pourriez le penser. Voir la réponse de @Hans.
Mike Atlas
@ Mike D'abord, j'ai dit que la façon dont il le faisait était correcte, et qu'il pouvait essayer de l'essayer / attraper en général. Je ne sais pas pourquoi vous (ou quelqu'un d'autre) m'avez donné un vote négatif alors que j'étais d'accord avec Hans, juste pour fournir une autre réponse que je ne m'attendais pas à obtenir un chèque. Ce n'est pas vraiment juste, et dire ensuite que l'alternative est fausse sans fournir de preuve sur la façon dont une exception qui peut être interceptée par le processus AppDomain UnhandledException qu'un try / catch dans Main ne peut pas intercepter. Je trouve impoli de dire que quelque chose ne va pas sans prouver pourquoi c'est mal, juste dire que c'est le cas, ne le fait pas.
Rodney S.Foley
5
J'ai affiché l'exemple que vous demandez. S'il vous plaît soyez responsable et supprimez vos votes négatifs non pertinents des anciennes réponses de Mike si vous ne l'avez pas fait. (Aucun intérêt personnel, tout simplement n'aime pas voir un tel abus du système.)
BlueMonkMN
3
Pourtant, vous jouez toujours le même "jeu" que lui, mais de façon pire parce que c'est une pure représaille, non basée sur la qualité d'une réponse. Ce n'est pas un moyen de résoudre le problème, mais de l'aggraver. C'est particulièrement mauvais lorsque vous ripostez contre quelqu'un qui avait même une inquiétude légitime à propos de votre réponse (comme je l'ai démontré).
BlueMonkMN
3
Oh, j'ajouterais également que le vote à la baisse n'est pas destiné aux personnes qui "sont un idiot complet ou violent les règles", mais plutôt de juger de la qualité d'une réponse. Il me semble que les réponses à un vote négatif afin de «commenter» la personne qui les fournit constituent un abus beaucoup plus important que les réponses à un vote négatif basées sur le contenu de la réponse elle-même, que ce vote soit exact ou non. Ne le prenez pas / ne le rendez pas si personnel.
BlueMonkMN