WPFアプリケーションで例外をグローバルにキャッチしますか?


242

実行時に例外がスローされるWPFアプリケーションがあります。未処理の例外をグローバルにキャッチしてログに記録しますが、それ以外は何も起こらなかったかのようにプログラムの実行を続行します(VBのようなものOn Error Resume Next)。

これはC#で可能ですか?もしそうなら、例外処理コードをどこに置く必要がありますか?

現在、try/をラップして、catch発生する可能性のあるすべての例外をキャッチできる単一のポイントを確認できません。そして、それでも私はキャッチのために実行されたものは何でも残していたでしょう。または私はここでひどく間違った方向に考えていますか?

ETA:以下の多くの人々がそれを指摘したので:アプリケーションは原子力発電所を制御するためのものではありません。クラッシュしたとしても、それほど大きな問題ではありませんが、UIに関連するランダムな例外は、それが使用される状況では問題になります。プラグインアーキテクチャを使用しており、他の人(その場合は学生もいるため完全にエラーのないコードを記述できる経験豊富な開発者はいない)によって拡張される可能性があるため、いくつかあります(おそらくまだあります)。

キャッチされる例外については、完全なスタックトレースを含めて、ログファイルに記録します。それがその演習の要点でした。文字通り、VBのOERNに私の類推をしている人々に対抗するためだけに。

特定のクラスのエラーを盲目的に無視することは危険であり、アプリケーションインスタンスを破損する可能性があることを知っています。前述のように、このプログラムは誰にとってもミッションクリティカルではありません。彼らの正しい心の中の誰もがその上に人間の文明の生存を賭けないでしょう。これは、特定の設計アプローチをwrtでテストするための単なる小さなツールです。ソフトウェア工学。

アプリケーションをすぐに使用する場合、例外で発生する可能性のあることは多くありません。

  • 例外処理なし–エラーダイアログとアプリケーションは終了します。実験は繰り返される必要がありますが、別の主題で可能性があります。エラーはログに記録されていませんが、残念です。
  • 一般的な例外処理–害のない無害なエラーがトラップされます。これは、開発中に発生したすべてのエラーから判断される一般的なケースです。この種のエラーを無視しても、直接的な影響はありません。コアデータ構造は十分にテストされているため、簡単にこれに耐えることができます。
  • 一般的な例外処理–重大なエラーがトラップされました。後でクラッシュする可能性があります。これはまれに発生する可能性があります。今まで見たことがありません。エラーはとにかく記録され、クラッシュは避けられないかもしれません。したがって、これは概念的には最初のケースと同様です。スタックトレースがあることを除いて。そして、ほとんどの場合、ユーザーは気付くことさえありません。

プログラムによって生成された実験データについて:深刻なエラーは最悪の場合、データが記録されないだけです。実験の結果をほんの少しだけ変えるような微妙な変更は、ほとんどあり得ません。その場合でも、結果が疑わしいと思われる場合は、エラーがログに記録されます。完全な外れ値である場合でも、そのデータポイントを破棄できます。

要約すると、はい、私自身は少なくとも部分的には正気であると考えています。また、プログラムを実行したままにするグローバル例外処理ルーチンは必ずしも完全に悪であるとは考えていません。前に2回言ったように、アプリケーションによっては、このような決定が有効な場合があります。この場合、それは有効な決定であると判断され、合計ではなく、でたらめです。他のアプリケーションでは、決定が異なる場合があります。しかし、私たちまたはエラーを無視しているという理由だけで、世界を爆破する可能性があるそのプロジェクトに取り組んだ他の人々を非難しないでください。

補足:そのアプリケーションのユーザーは1人だけです。WindowsやOfficeのように何百万人もが使用しているものではなく、例外をユーザーにバブルさせるコストは、そもそも非常に異なっています。


これは実際には考えていません-はい、おそらくアプリケーションを終了させたいでしょう。しかし、StackTraceを使用して最初に例外をログに記録しておくのは良いことではありませんか ユーザーから受け取ったのが「このボタンを押したときにアプリケーションがクラッシュした」という場合は、十分な情報がないため、問題を解決できない可能性があります。しかし、アプリケーションをより快適に中止する前に、最初に例外をログに記録した場合、かなり多くの情報が得られます。
ラス

その点については、質問の中で少し詳しく説明しました。私は関連するリスクを知っており、その特定のアプリケーションではそれは許容できると見なされました。また、UIが素晴らしいアニメーションを実行しようとしたときに、インデックスの範囲外の単純なもののためにアプリケーションを中止することは、やりすぎであり、不要です。はい、正確な原因はわかりませんが、エラーのケースの大部分は良性であるという主張を裏付けるデータがあります。マスクしている深刻なものはアプリケーションのクラッシュを引き起こす可能性がありますが、それでもグローバル例外処理がなければ発生します。
Joey、

もう1つの注意点:この方法でクラッシュを防ぐと、ユーザーはそれを気に入るはずです。
lahjaton_j 2016年

Visual Studio 2010用のC#でのWPF(ハンドラーの最も完全なコレクション)サンプルの未処理の例外のWindows処理を参照してください。AppDomain.CurrentDomain.FirstChanceException、Application.DispatcherUnhandledException、AppDomain.CurrentDomain.UnhandledExceptionなど、5つの例があります。
user34660 2017年

On Error Resume NextC#ではVBのようなコードフローを実行できないことを追加します。Exception(C#には「エラー」がない)後は、次のステートメントで単純に再開することはできません。実行はcatchブロックで続行されます-または以下の回答で説明されているイベントハンドラーの1つで続行されます。
マイク

回答:


191

を使用しApplication.DispatcherUnhandledException Eventます。概要については、この質問を参照してください(Drew Noakesの回答を参照)。

データベースに保存しようとしているときにスタックオーバーフロー、メモリ不足、ネットワーク接続が失われた後など、アプリケーションの正常な再開を妨げる例外がまだあることに注意してください。


ありがとう。そして、はい、回復できない例外があることは承知していますが、この場合、発生する可能性のある例外のほとんどは無害です。
ジョーイ

14
例外がバックグラウンドスレッドで発生した場合(たとえば、ThreadPool.QueueUserWorkItemを使用)、これは機能しません。
Szymon Rozga、2009

@siz:良い点ですが、それらは作業項目の通常のtryブロックで処理できますか?
デビッドシュミット

1
@PitiOngmongkolkul:ハンドラーはメインループからイベントとして呼び出されます。イベントハンドラーが戻ると、アプリは通常どおり続行されます。
デビッドシュミット2013

4
プログラムを終了するデフォルトのハンドラーをスキップするには、e.Handled = true(eはDispatcherUnhandledExceptionEventArgs)を設定する必要があるようです。msdn.microsoft.com/en-us/library/...
ピティOngmongkolkul

55

AppLoginのすべてのスレッドUIディスパッチャースレッド、および非同期関数からスローされた例外をキャッチするNLogを使用したサンプルコード:

App.xaml.cs:

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }

10
これはここで最も完全な答えです!タスクスケジューラの例外が含まれています。私にとっては、クリーンでシンプルなコードが最適です。
Nikola Jovic 2018

3
最近、顧客でApp.configが破損しました。NLogがApp.configから読み取ろうとして例外をスローしたため、彼女のアプリは起動していませんでした。その例外は静的ロガー初期化子にあったため、UnhandledExceptionハンドラーによってキャッチされませんでした。私は...起こっていたものを見つけるために、Windowsイベントログビューアを見ていた
heltonbiker

アプリケーションをUI例外でクラッシュしないように設定e.Handled = true;するUnhandledExceptionことをお
勧めし

30

AppDomain.UnhandledExceptionイベント

このイベントは、キャッチされなかった例外を通知します。これにより、アプリケーションは、システムのデフォルトハンドラーがユーザーに例外を報告してアプリケーションを終了する前に、例外に関する情報をログに記録できます。

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

UnhandledExceptionイベントがデフォルトのアプリケーションドメインで処理される場合、スレッドがどのアプリケーションドメインで開始されたかに関係なく、スレッドで未処理の例外が発生するとそこで発生します。UnhandledExceptionのイベントハンドラーがあるアプリケーションドメインでスレッドが開始された場合、イベントはそのアプリケーションドメインで発生します。そのアプリケーションドメインがデフォルトのアプリケーションドメインではなく、デフォルトのアプリケーションドメインにもイベントハンドラがある場合、両方のアプリケーションドメインでイベントが発生します。

たとえば、スレッドがアプリケーションドメイン「AD1」で開始し、アプリケーションドメイン「AD2」のメソッドを呼び出し、そこからアプリケーションドメイン「AD3」のメソッドを呼び出し、そこで例外がスローされたとします。UnhandledExceptionイベントを発生させることができる最初のアプリケーションドメインは "AD1"です。そのアプリケーションドメインがデフォルトのアプリケーションドメインでない場合、イベントはデフォルトのアプリケーションドメインでも発生する可能性があります。


(コンソールアプリとWinFormsアプリのみを話すと思う)を指すURLからコピーします。「。NET Framework 4以降、このイベントは、スタックオーバーフローやイベントハンドラがセキュリティ上重要で、HandleProcessCorruptedStateExceptionsAttribute属性を持たない限り、アクセス違反。」
George Birbilis、2015年

1
@GeorgeBirbilis:AppコンストラクターでUnhandledExceptionイベントをサブスクライブできます。
CharithJ 2015年

18

ここで他の人が言及したことに加えて、 Application.DispatcherUnhandledException(およびその類似物)と

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

で、app.configセカンダリスレッド例外がアプリケーションをシャットダウンするのを防ぎます。


2

ここに使用する完全な例があります NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}

-4

「VBのOn Error Resume Next?」のように ちょっと怖いですね。最初の推奨はそれをしないことです。2番目の推奨事項は、それを実行せず、それについて考えないことです。障害をより適切に分離する必要があります。この問題への取り組み方は、コードの構造によって異なります。MVCなどのパターンを使用している場合、これはそれほど難しくはなく、グローバル例外スワローを必要としません。次に、log4netのような優れたロギングライブラリを探すか、トレースを使用します。どのような例外について話しているのか、アプリケーションのどの部分で例外がスローされるのかなど、詳細を知る必要があります。


2
ポイントは、キャッチされない例外が発生した後でもアプリケーションを実行し続けたいということです。私はそれらをログに記録したいだけで(私たちのニーズに基づいて、このためのカスタムログフレームワークがあります)、一部のプラグインがユーザーインタラクションコードで奇妙なことをしたからといって、プログラム全体を中止する必要はありません。これまでに見てきたことから、さまざまな部分が実際にはお互いを認識していないため、原因を明確に特定することは簡単ではありません。
ジョーイ

9
私は理解しますが、あなたが調べない例外の後に継続することに非常に慎重でなければなりません、考慮すべきデータ破損などの可能性があります。
BobbyShaftoe 2009

3
私はBobbyShaftoeに同意します。これは正しい方法ではありません。アプリケーションをクラッシュさせます。障害がプラグインにある場合は、修正するか、プラグインを修正してもらいます。実行したままにすることは非常に危険です。奇妙な副作用が発生し、アプリケーションで発生しているバグの論理的な説明を見つけることができなくなります。

1
その点については、質問の中で少し詳しく説明しました。私は関連するリスクを知っており、その特定のアプリケーションではそれは許容できると見なされました。また、UIが素晴らしいアニメーションを実行しようとしたときに、インデックスの範囲外の単純なもののためにアプリケーションを中止することは、やりすぎであり、不要です。はい、正確な原因はわかりませんが、エラーのケースの大部分は良性であるという主張を裏付けるデータがあります。マスクしている深刻なものはアプリケーションのクラッシュを引き起こす可能性がありますが、それでもグローバル例外処理が行われなかった場合に起こります。
Joey、

これはコメントではなく、回答である必要があります。「これをどうやってやるのか」に「あなたはおそらくすべきではない」と答えることは答えではなく、コメントです。
Josh Noe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.