コンソールアプリケーションの.NETグローバル例外ハンドラ


198

質問:コンソールアプリケーションで未処理の例外のグローバル例外ハンドラーを定義したいと思います。asp.netでは、global.asaxで定義でき、Windowsアプリケーション/ servicesでは、次のように定義できます。

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

しかし、コンソールアプリケーションのグローバル例外ハンドラを定義するにはどうすればよいですか?
currentDomainが機能しないようです(.NET 2.0)?

編集:

ああ、愚かな間違い。
VB.NETでは、currentDomainの前に「AddHandler」キーワードを追加する必要があります。そうしないと、IntelliSenseでUnhandledExceptionイベントが表示されません...
これは、VB.NETとC#コンパイラがイベント処理を異なる方法で処理するためです。

回答:


283

いいえ、それが正しい方法です。これは期待どおりに機能し、おそらく次のように機能します。

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

この方法では、ジッターによって生成されるタイプおよびファイルロードの例外をキャッチできないことに注意してください。Main()メソッドが実行を開始する前に発生します。それらをキャッチするには、ジッターを遅延させ、危険なコードを別のメソッドに移動し、それに[MethodImpl(MethodImplOptions.NoInlining)]属性を適用する必要があります。


3
ここで提案したことを実装しましたが、アプリケーションを終了したくありません。私はちょうどそれをログに記録し、プロセス(せずに続行したいConsole.ReadLine()か、プログラムの流れの他の乱れをしかし、私は取得すると、例外の再引き上げ何度も何度もあり、そして再び。。

3
@Shahrooz Jefri:未処理の例外が発生すると、続行できません。スタックがめちゃくちゃになっていて、これがターミナルです。サーバーがある場合、UnhandledExceptionTrapperで実行できることは、同じコマンドライン引数でプログラムを再起動することです。
Stefan Steiger 2013

6
それは間違いなくあります!ここでは、Application.ThreadExceptionイベントについて説明していません。
ハンスパッサント2013

4
この回答と返信のコメントを理解するための鍵は、このコードが今は例外を検出することを示しているが、続行するオプションがある通常のtry / catchブロックのように完全に「処理」しないことを理解することだと思います。詳細については、他の回答を参照してください。この方法で例外を「処理」すると、別のスレッドで例外が発生したときに実行を継続するオプションがなくなります。その意味で、「処理」とは「処理して実行を続ける」という意味の場合、この回答は例外を完全に「処理」しないと言えます。
BlueMonkMN 2014年

4
言うまでもなく、さまざまなスレッドで実行するテクノロジーはたくさんあります。たとえば、タスク並列ライブラリ(TPL)には、未処理の例外をキャッチする独自の方法があります。つまり、これがすべての状況で機能するわけではないというのは一種の滑稽なことです。C#のすべてに対応できる場所は1つではありませんが、使用するテクノロジーによっては、さまざまな場所に接続できます。
Doug

23

シングルスレッドアプリケーションの場合は、Main関数で単純なtry / catchを使用できますが、これは、他のスレッドなど、Main関数の外部でスローされる可能性のある例外をカバーしていません(他のコメント)。このコードは、Mainで例外を処理しようとした場合でも、例外が原因でアプリケーションが終了する方法を示しています(Enterキーを押して例外が発生する前にアプリケーションを正常に終了させると、プログラムが正常に終了することに注意してください。 、それは非常に不幸に終了します):

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;
   }
}

アプリケーションが終了する前に別のスレッドが例外をスローしてクリーンアップを実行したときに通知を受け取ることができますが、私の知る限り、コンソールアプリケーションからは、例外を処理しないとアプリケーションを強制的に実行し続けることができません。いくつかのあいまいな互換性オプションを使用せずにアプリケーションが.NET 1.xの場合と同じように動作するようにすることなく、それがスローされるスレッド上で。このコードは、メインスレッドに他のスレッドからの例外を通知する方法を示していますが、それでも不幸に終了します。

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;
   }
}

したがって、私の意見では、コンソールアプリケーションでそれを処理する最もクリーンな方法は、すべてのスレッドがルートレベルで例外ハンドラーを持つようにすることです。

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

TRY CATCHはリリースモードでは予期しないエラーのため機能しません:/
Muflix

12

スレッドからの例外も処理する必要があります。

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

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

Winformsについて申し訳ありませんが、コンソールアプリケーションで使用しているすべてのスレッドについて、try / catchブロックで囲む必要があります。未処理の例外が発生したバックグラウンドスレッドは、アプリケーションを終了させません。


1

私は古いVB.NETコンソールアプリケーションを継承し、グローバル例外ハンドラーをセットアップする必要がありました。この質問はVB.NETについて何回か言及し、VB.NETでタグ付けされていますが、他のすべての回答はC#にあるため、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

REMここでは、スタックオーバーフローが構文の強調表示を少しよく処理しているように見えるため、ここでは一重引用符の代わりにコメントマーカーを使用しましたREM


-13

あなたがしようとしていることは、.Net 2.0のMSDNドキュメントに従って動作するはずです。また、コンソールアプリのエントリポイントを中心に、メインでtry / catchを試すこともできます。

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

そして今あなたのキャッチはキャッチされないものを処理します(メインスレッドで)。それは優雅であることができて、あなたが望むならそれがあったところから再開することさえできます、あるいはあなたはただアプリを死にさせて例外を記録することができます。クリーンアップを行う場合は、finallyを追加します。 各スレッドには、メインと同様の独自の高レベルの例外処理が必要です。

BlueMonkMNによって指摘され、彼の回答で詳細に示されているスレッドに関するポイントを明確にするために編集されました。


1
残念ながら、実際には例外がMain()ブロックの外でスローされる可能性があります。これは、実際のところ、「すべてをキャッチ」するものではありません。@ハンスの答えを見てください。
Mike Atlas

@マイク最初私は彼がそれをしている方法は正しいと彼はメインでトライ/キャッチを試すことができると言った。私がハンス氏に同意するときに、小切手を受け取ることを期待していなかった別の回答を提供したのに、なぜあなた(または他の誰か)が私に反対票を投じたのかわかりません。それは実際には公平ではなく、代わりにMainのtry / catchがキャッチできないAppDomain UnhandledExceptionプロセスがキャッチできる例外に関する証拠を提供せずに、代替案が間違っていると言います。私は、なぜそれが間違っているのかを証明せずに、間違っていると言うのは失礼だと思います。
Rodney S. Foley

5
私はあなたが求めている例を投稿しました。責任がある場合は、責任を負わないで、マイクの古い回答から無関係な反対票を削除してください。(個人的な関心はなく、このようなシステムの悪用を見たくないだけです。)
BlueMonkMN 2010年

3
それでも、あなたは彼と同じ「ゲーム」をプレイしますが、それは答えの質に基づくのではなく、純粋な報復であるため、さらに悪い方法でしかありません。それは問題を解決する方法ではなく、悪化させるだけです。(私が示したように)あなたの答えについて正当な懸念さえ持っている誰かに対して報復するとき、それは特に悪いです。
BlueMonkMN 2010年

3
ああ、私はまた、反対投票は「完全なばかであるか、ルールに違反している」人々のためではなく、むしろ回答の質を判断するためのものであると付け加えます。回答の提供者に「コメント」するための反対投票の回答は、投票が正確であるかどうかに関係なく、回答自体の内容に基づく反対投票よりもはるかに悪用のようです。個人的なものにしないでください。
BlueMonkMN 2010年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.