web-dev-qa-db-ja.com

WPFグローバル例外ハンドラー

時々、再現性のない状況で、WPFアプリケーションがメッセージなしでクラッシュします。アプリケーションはすぐに終了します。

グローバルなTry/Catchブロックを実装するのに最適な場所はどこですか。少なくとも、「ご不便をおかけして申し訳ありません...」というメッセージボックスを実装する必要があります。

308
Scott Olson

AppDomain.UnhandledException イベントを処理できます

編集:実際には、このイベントはおそらくもっと適切です。 Application.DispatcherUnhandledException

155
Thomas Levesque

さまざまなレベルで未処理の例外をトラップできます。

  1. AppDomain.CurrentDomain.UnhandledException AppDomain内のすべてのスレッドから。
  2. Dispatcher.UnhandledException 単一の特定のUIディスパッチャスレッドから。
  3. Application.Current.DispatcherUnhandledException WPFアプリケーションのmainUIディスパッチャースレッドから。
  4. TaskScheduler.UnobservedTaskException 非同期操作にタスクスケジューラを使用する各AppDomain内から。

未処理の例外をトラップする必要があるレベルを検討する必要があります。

#2と#3のどちらを決定するかは、複数のWPFスレッドを使用しているかどうかによって異なります。これは非常にエキゾチックな状況であり、あなたがそうであるかどうかわからない場合は、そうではない可能性が高いです。

479
Drew Noakes

Application.Dispatcher.UnhandledExceptionのコードの簡単な例:

public App() {
    this.Dispatcher.UnhandledException += OnDispatcherUnhandledException;
}

void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
    string errorMessage = string.Format("An unhandled exception occurred: {0}", e.Exception.Message);
    MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    // OR whatever you want like logging etc. MessageBox it's just example
    // for quick debugging etc.
    e.Handled = true;
}

このコードをApp.xaml.csに追加しました

106
Sergey

WPFアプリで次のコードを使用して、未処理の例外が発生するたびに「ご不便をおかけして申し訳ありません」ダイアログボックスを表示します。例外メッセージを表示し、アプリを閉じるか、例外を無視して続行するかをユーザーに尋ねます(後者の場合は、致命的でない例外が発生し、ユーザーが引き続きアプリを引き続き使用できる場合に便利です)。

App.xamlで、スタートアップイベントハンドラーを追加します。

<Application .... Startup="Application_Startup">

App.xaml.csコードで、グローバルアプリケーションイベントハンドラーを登録するStartupイベントハンドラー関数を追加します。

using System.Windows.Threading;

private void Application_Startup(object sender, StartupEventArgs e)
{
    // Global exception handling  
    Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(AppDispatcherUnhandledException);    
}

void AppDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{    
    \#if DEBUG   // In debug mode do not custom-handle the exception, let Visual Studio handle it

    e.Handled = false;

    \#else

    ShowUnhandledException(e);    

    \#endif     
}

void ShowUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{
    e.Handled = true;

    string errorMessage = string.Format("An application error occurred.\nPlease check whether your data is correct and repeat the action. If this error occurs again there seems to be a more serious malfunction in the application, and you better close it.\n\nError: {0}\n\nDo you want to continue?\n(if you click Yes you will continue with your work, if you click No the application will close)",

    e.Exception.Message + (e.Exception.InnerException != null ? "\n" + 
    e.Exception.InnerException.Message : null));

    if (MessageBox.Show(errorMessage, "Application Error", MessageBoxButton.YesNoCancel, MessageBoxImage.Error) == MessageBoxResult.No)   {
        if (MessageBox.Show("WARNING: The application will close. Any changes will not be saved!\nDo you really want to close it?", "Close the application!", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        Application.Current.Shutdown();
    } 
}
40
jurev

最良の答えはおそらく https://stackoverflow.com/a/1472562/60199 です。

使用方法を示すコードを次に示します。

App.xaml.cs

public sealed partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // setting up the Dependency Injection container
        var resolver = ResolverFactory.Get();

        // getting the ILogger or ILog interface
        var logger = resolver.Resolve<ILogger>();
        RegisterGlobalExceptionHandling(logger);

        // Bootstrapping Dependency Injection 
        // injects ViewModel into MainWindow.xaml
        // remember to remove the StartupUri attribute in App.xaml
        var mainWindow = resolver.Resolve<Pages.MainWindow>();
        mainWindow.Show();
    }

    private void RegisterGlobalExceptionHandling(ILogger log)
    {
        // this is the line you really want 
        AppDomain.CurrentDomain.UnhandledException += 
            (sender, args) => CurrentDomainOnUnhandledException(args, log);

        // optional: hooking up some more handlers
        // remember that you need to hook up additional handlers when 
        // logging from other dispatchers, shedulers, or applications

        Application.Dispatcher.UnhandledException += 
            (sender, args) => DispatcherOnUnhandledException(args, log);

        Application.Current.DispatcherUnhandledException +=
            (sender, args) => CurrentOnDispatcherUnhandledException(args, log);

        TaskScheduler.UnobservedTaskException += 
            (sender, args) => TaskSchedulerOnUnobservedTaskException(args, log);
    }

    private static void TaskSchedulerOnUnobservedTaskException(UnobservedTaskExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        args.SetObserved();
    }

    private static void CurrentOnDispatcherUnhandledException(DispatcherUnhandledExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        // args.Handled = true;
    }

    private static void DispatcherOnUnhandledException(DispatcherUnhandledExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        // args.Handled = true;
    }

    private static void CurrentDomainOnUnhandledException(UnhandledExceptionEventArgs args, ILogger log)
    {
        var exception = args.ExceptionObject as Exception;
        var terminatingMessage = args.IsTerminating ? " The application is terminating." : string.Empty;
        var exceptionMessage = exception?.Message ?? "An unmanaged exception occured.";
        var message = string.Concat(exceptionMessage, terminatingMessage);
        log.Error(exception, message);
    }
}
14
MovGP0

上記の投稿に加えて:

Application.Current.DispatcherUnhandledException

メインスレッドから別のスレッドからスローされた例外をキャッチしません。実際のスレッドでこれらの例外を処理する必要があります。ただし、グローバル例外ハンドラでそれらを処理する場合は、メインスレッドに渡すことができます。

 System.Threading.Thread t = new System.Threading.Thread(() =>
    {
        try
        {
            ...
            //this exception will not be catched by 
            //Application.DispatcherUnhandledException
            throw new Exception("huh..");
            ...
        }
        catch (Exception ex)
        {
            //But we can handle it in the throwing thread
            //and pass it to the main thread wehre Application.
            //DispatcherUnhandledException can handle it
            System.Windows.Application.Current.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action<Exception>((exc) =>
                    {
                      throw new Exception("Exception from another Thread", exc);
                    }), ex);
        }
    });
11
Tobias Hoefer

Thomasの答えを補足するために、Applicationクラスには、処理可能な DispatcherUnhandledException イベントもあります。

3
dustyburwell

完全なソリューションは here です

サンプルコードを使用して非常にわかりやすく説明されています。ただし、アプリケーションを閉じないように注意してください。Application.Current.Shutdown();行を追加します。アプリを正常に閉じます。

3
karpanai

上記のように

Application.Current.DispatcherUnhandledExceptionは、メインスレッド以外の別のスレッドからスローされた例外をキャッチしません。

実際には、スレッドの作成方法に依存します

Application.Current.DispatcherUnhandledExceptionによって処理されないケースの1つは、Application.ThreadExceptionを設定する必要があるメインスレッド以外のスレッドでFormsを実行する場合にApplication.ThreadExceptionを使用してこれらを処理できるSystem.Windows.Forms.Timerです。そのような各スレッドから

1
Jens