在WPF应用程序中全局捕获异常?


242

我们有一个WPF应用程序,其中的某些部分可能会在运行时引发异常。我想全局捕获任何未处理的异常并将它们记录下来,但是否则继续执行程序,就好像什么都没发生一样(有点像VB的那样On Error Resume Next)。

这在C#中可能吗?如果是这样,我到底需要在哪里放置异常处理代码?

目前,我看不到任何可以包裹try/的点,catch并且可以捕获所有可能发生的异常。即使那样,我仍然会因为捕获而留下任何已执行的内容。还是我在这里以错误的方向思考?

ETA:因为下面的许多人指出:该应用程序不是用于控制核电厂的。如果它崩溃了,那没什么大不了的,但是大多数与UI相关的随机异常在使用它的上下文中是很麻烦的。有(并且可能仍然有)其中的一些,因为它使用了插件架构,并且可能会被其他人扩展(在这种情况下也是学生;因此,没有经验的开发人员能够编写完全无错误的代码)。

至于捕获的异常:我确实将它们记录到日志文件中,包括完整的堆栈跟踪。这就是整个练习的重点。只是为了反驳那些从字面上把我比作VB的OERN的人。

我知道盲目地忽略某些错误类别是危险的,并且可能会损坏我的应用程序实例。如前所述,该程序对任何人都不是关键任务。在他们的正确思想中,没有人会相信人类文明的生存。它只是一个用于测试某些设计方法的小工具。软件工程。

为了立即使用该应用程序,异常不会发生很多事情:

  • 没有异常处理-错误对话框和应用程序退出。必须重复实验,尽管可能要对另一个主题进行。尚未记录任何错误,这是不幸的。
  • 通用异常处理–良性错误被捕获,没有造成危害。从我们在开发过程中看到的所有错误来看,这应该是常见的情况。忽略这种错误不会立即产生后果;核心数据结构已经过充分测试,因此很容易幸免。
  • 通用异常处理–严重的错误被困,可能在以后崩溃。这可能很少发生。到目前为止,我们从未见过。无论如何,都会记录该错误,并且崩溃是不可避免的。因此,这在概念上与第一种情况相似。除了我们有堆栈跟踪。在大多数情况下,用户甚至不会注意到。

至于程序生成的实验数据:严重的错误在最坏的情况下只会导致不记录任何数据。细微的改变几乎不可能改变实验结果。即使在这种情况下,如果结果看起来可疑,也会记录错误;如果这是一个总体异常值,仍然可以丢弃该数据点。

总结一下:是的,我认为自己至少仍然是部分理智的人,并且我不认为会导致程序运行必定完全有害的全局异常处理例程。如前所述,取决于应用程序,这样的决定可能是有效的。在这种情况下,它被认为是一个有效的决定,而不是胡说八道。对于任何其他应用程序,该决定可能看起来有所不同。但是请不要指责我或从事该项目的其他人可能因为我们无视错误而震惊了整个世界。

旁注:该应用程序只有一个用户。并不是像Windows或Office那样被成千上万的人所使用,因为在这种情况下,首先让用户冒起泡沫的成本已经完全不同了。


这不是真正的想法-是的,您可能希望应用程序退出。但是,先用StackTrace记录异常不是很好吗?如果从用户那里得到的只是“当我按下此按钮时您的应用程序崩溃了”,则可能由于您没有足够的信息而无法解决该问题。但是,如果您在更愉快地中止应用程序之前先记录了异常,那么您将获得更多的信息。
拉斯

我现在在问题中稍微阐述了这一点。我知道所涉及的风险,对于该特定应用程序,它被认为是可以接受的。在UI试图做一些漂亮的动画时,中止应用程序的操作就像索引超出范围一样简单,这多余的,而且是不需要的。是的,我不知道确切的原因,但是我们有数据可以证明绝大多数错误情况都是良性的。我们掩盖的严重错误可能会导致应用程序崩溃,但是如果没有全局异常处理,那就会发生这种情况。
乔伊,

另一个注意事项:如果您使用这种方法防止崩溃,则用户很可能会喜欢它。
lahjaton_j

请参阅Visual Studio 2010的C#中WPF(最完整的处理程序集合)示例中的Windows处理未处理的异常。它有5个示例,包括AppDomain.CurrentDomain.FirstChanceException,Application.DispatcherUnhandledException和AppDomain.CurrentDomain.UnhandledException。
user34660

我想补充一点,On Error Resume Next在C#中不可能实现类似VB的代码流。在Exception(C#没有“错误”)之后,您不能简单地继续执行下一条语句:执行将在一个catch块中继续进行-或在以下答案中所述的事件处理程序之一中继续进行。
迈克

Answers:


191

使用Application.DispatcherUnhandledException Event。请参阅此问题以获取摘要(请参阅Drew Noakes的答案)。

请注意,仍然有一些异常会阻止您的应用程序成功恢复,例如在尝试将其保存到数据库时出现堆栈溢出,内存耗尽或网络连接丢失之后。


谢谢。是的,我知道有些异常无法恢复,但在这种情况下,绝大多数可能发生的异常都是良性的。
乔伊,

14
如果您的异常发生在后台线程上(例如,使用ThreadPool.QueueUserWorkItem),则它将无法正常工作。
Szymon Rozga,2009年

@siz:很好,但是可以在工作项中使用正常的try块来处理,不是吗?
David Schmitt,2009年

1
@PitiOngmongkolkul:处理程序被称为主循环中的事件。当事件处理程序返回时,您的应用将继续正常运行。
David Schmitt

4
似乎我们需要设置e.Handled = true(其中e是DispatcherUnhandledExceptionEventArgs),以跳过退出程序的默认处理程序。msdn.microsoft.com/en-us/library/...
皮提Ongmongkolkul

55

示例代码中使用NLOG,将捕获从抛出的异常的AppDomain中的所有线程,从UI线程调度,并从异步功能

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
这是最完整的答案!Taks计划程序例外包括在内。最适合我的干净简洁的代码。
Nikola Jovic

3
最近,我有一个客户的App.config损坏,她的App甚至没有启动,因为NLog试图读取App.config并引发异常。由于该异常在静态logger初始化程序中,因此不会被UnhandledException处理程序捕获。我必须看一下Windows事件日志查看器才能找到正在发生的事情……
heltonbiker

我建议设置e.Handled = true;UnhandledException,应用程序不会在UI异常时崩溃
Apfelkuacha

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”。如果该应用程序域不是默认应用程序域,那么也可以在默认应用程序域中引发该事件。


从您指向的URL复制(我认为仅涉及控制台应用程序和WinForms应用程序):“从.NET Framework 4开始,对于破坏进程状态的异常(例如堆栈溢出或除非事件处理程序对安全性至关重要并且具有HandleProcessCorruptedStateExceptionsAttribute属性,否则将发生访问冲突。”
George Birbilis 2015年

1
@GeorgeBirbilis:您可以在App构造函数中订阅UnhandledException事件。
CharithJ

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?” 听起来有点吓人。第一个建议是不要这样做。第二个建议是不要这样做,也不要考虑它。您需要更好地隔离故障。至于如何解决这个问题,则取决于您的代码结构。如果您使用的是MVC之类的模式,那么这应该不太困难,而且绝对不需要全局异常吞并程序。其次,寻找一个好的日志库,例如log4net或使用跟踪。我们需要了解更多详细信息,例如您正在谈论的异常类型以及应用程序的哪些部分可能导致引发异常。


2
关键是,即使在未捕获的异常发生后,我也要保持应用程序运行。我只想记录它们(根据需要,我们为此提供了一个自定义的记录框架),而不必仅仅因为某些插件在其用户交互代码中做了一些奇怪的操作而中止了整个程序。从目前为止我所看到的,干净地找出原因并非易事,因为不同部分之间并不真正地相互了解。
乔伊(Joey)

9
我了解,但是您必须非常谨慎,在未检查的异常之后继续操作,有可能发生数据损坏等问题。
BobbyShaftoe

3
我同意BobbyShaftoe。这不是正确的方法。让应用程序崩溃。如果故障出在某个插件上,请修复或找人修复该插件。让它继续运行非常危险。您将得到怪异的副作用,并且将无法找到有关应用程序中发生的错误的逻辑解释。

1
我现在在问题中稍微阐述了这一点。我知道所涉及的风险,对于该特定应用程序,它被认为是可以接受的。在UI试图做一些漂亮的动画时,中止应用程序的操作就像索引超出范围一样简单,这是多余的并且不需要的。是的,我不知道确切的原因,但是我们有数据可以证明绝大多数错误情况都是良性的。我们掩盖的严重错误可能会导致应用程序崩溃,但是如果没有全局异常处理,那就会发生这种情况。
乔伊,

这应该是评论,而不是答案。用“您可能不应该”回应“我该怎么做”不是答案,而是一种评论。
Josh Noe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.