【问题标题】:WPF global exception handler [duplicate]WPF全局异常处理程序[重复]
【发布时间】:2010-12-01 03:29:08
【问题描述】:

有时,在不可重现的情况下,我的 WPF 应用程序在没有任何消息的情况下崩溃。应用程序会立即关闭。

哪里是实现全局 Try/Catch 块的最佳位置。至少我必须实现一个消息框:“抱歉给您带来不便......”

【问题讨论】:

  • 一定会喜欢重复链接回这个问题的方式
  • 这个问题有更好的答案。

标签: c# .net wpf exception exception-handling


【解决方案1】:

你可以处理AppDomain.UnhandledException事件

编辑:实际上,这个事件可能更合适:Application.DispatcherUnhandledException

【讨论】:

  • 在表单构造函数中添加处理程序,如下所示:AppDomain.Current.UnhandledException+=...
  • 如果你创建多个窗口实例是个坏主意...
  • 您好 Thomas,感谢您的回答。 Appdomain.UnHandledException 非常适合我。
  • 我猜可以在 App.xaml.cs 添加处理程序
  • @FranzKiermaier 你使用 MVVM 的事实不应该改变任何东西......
【解决方案2】:

您可以在不同级别捕获未处理的异常:

  1. AppDomain.CurrentDomain.UnhandledException 来自 AppDomain 中的所有线程。
  2. Dispatcher.UnhandledException 来自单个特定的 UI 调度程序线程。
  3. Application.Current.DispatcherUnhandledException 来自 WPF 应用程序中的 main UI 调度程序线程。
  4. TaskScheduler.UnobservedTaskException 来自每个使用任务调度程序进行异步操作的 AppDomain。

您应该考虑需要在什么级别捕获未处理的异常。

在#2 和#3 之间做出决定取决于您是否使用多个 WPF 线程。这是一个非常奇特的情况,如果您不确定自己是否是,那么您很可能不是。

【讨论】:

  • 注意您的第 3 项,我必须将 .Current 放在 Application 后面,如下所示:Application.Current.DispatcherUnhandledException += ...
  • @Keith G -- 所有这些事件都是实例成员,因此您需要根据具体情况为所需的每个对象挂钩它们。
  • 另外,我们必须使用 AppDomain.CurrentDomain.UnhandledException 作为项目 #1。
  • 如果你现在使用异步任务/TaskScheduler,我相信TaskScheduler.UnobservedTaskException += ... 也与这些正交。
  • 很高兴看到选项的汇编以及每个选项的解释。以下是我目前在应用程序级别处理未处理异常的方法:gist.github.com/ronnieoverby/7568387
【解决方案3】:

为了补充 Thomas 的回答,Application 类还具有您可以处理的 DispatcherUnhandledException 事件。

【讨论】:

    【解决方案4】:

    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 中添加了这段代码

    【讨论】:

    • +1 用于剪切/粘贴代码。如果您希望为错误消息对话框增添趣味,WPF 扩展工具包有一个messagebox control
    • 请注意,在某些情况下,设置e.Handled = true 可能会导致应用程序UI 关闭,而进程仍然在机器上静默运行。
    • 警告!在未处理的顶级异常中使用 MessageBox.Show() 可能会导致进程挂起!记录或执行其他操作,但不要使用此 API 或显示任何 UI。
    • @qJake 你能更好地描述一下这种情况吗?
    • e.Handled = true 之后剩余的进程示例是在启动窗口的构造函数中引发异常时。窗口永远不会显示,但由于处理了异常,应用程序永远不会崩溃,因此应用程序保持运行。我还不确定处理这个问题的最佳方法。 if (!(e is System.Windows.Markup.XamlParseException)) args.Handled = true; 似乎有效,但感觉不是最好的解决方案。
    【解决方案5】:

    每当发生未处理的异常时,我在我的 WPF 应用程序中使用以下代码来显示“抱歉给您带来的不便”对话框。它显示异常消息,并询问用户是要关闭应用程序还是忽略异常并继续(后一种情况在发生非致命异常并且用户仍然可以正常继续使用应用程序时很方便)。

    在 App.xaml 中添加 Startup 事件处理程序:

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

    【讨论】:

    • @Weston 链接已失效
    • @McKay 因受骗而被删除:stackoverflow.com/questions/2004629/…
    • @McKay 没问题。仅供参考,它引发了一个元问题meta.stackoverflow.com/questions/300452/…
    • 只是关闭而不给用户保存更改的机会仍然有点邪恶。如果是临时的网络故障怎么办?
    • 警告!在未处理的顶级异常中使用 MessageBox.Show() 可能会导致进程挂起!记录或执行其他操作,但不要使用此 API 或显示任何 UI。
    【解决方案6】:

    除了上面的帖子:

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

    【讨论】:

    • 这一行 System.Windows.Threading.DispatcherPriority.Normal 让我终于在给定的 DispatcherUnhandledException 事件处理程序上触发异常
    【解决方案7】:

    如上所述

    Application.Current.DispatcherUnhandledException 不会捕获 从另一个线程然后是主线程抛出的异常。

    这实际上取决于线程是如何创建的

    Application.Current.DispatcherUnhandledException 未处理的一种情况是 System.Windows.Forms.Timer,Application.ThreadException 可用于处理这些情况 如果您在主线程以外的其他线程上运行 Forms,则需要从每个此类线程设置 Application.ThreadException

    【讨论】:

    • 从 SO 的其他线程复制 Hertzel Guinness 的答案: app.config 中的 将阻止关闭应用程序的辅助线程异常”
    【解决方案8】:

    完整的解决方案是here

    用示例代码很好地解释了它。但是,请注意它不会关闭应用程序。添加该行 Application.Current.Shutdown(); 优雅地关闭应用程序。

    【讨论】:

      【解决方案9】:

      最佳答案可能是https://stackoverflow.com/a/1472562/601990

      这里有一些代码说明如何使用它:

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

      【讨论】:

      • 可能需要包含#if DEBUG,以便Visual Studio 仅在调试时正常处理异常。很棒的解决方案。
      • 您应该使用RegisterGlobalExceptionHandling 上的[Conditional("DEBUG")] 属性而不是#if DEBUG。这样就可以确保在更改编译器目标时代码可以编译。
      • 此外,最好在生产代码中也保留全局异常的日志记录。您可以使用ConditionalAttribute 在依赖注入设置中配置记录器,只需更改记录详细程度。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-17
      • 2012-12-31
      • 2011-05-19
      • 2011-08-31
      相关资源
      最近更新 更多