关于C#:在WPF应用程序中全局捕获异常?

Globally catch exceptions in a WPF application?

我们有一个WPF应用程序,其中的某些部分可能在运行时抛出异常。我希望全局捕获任何未经处理的异常并将其记录下来,否则将继续执行程序,就像什么都没有发生一样(类似于vb的On Error Resume Next)。

这在C中是可能的吗?如果是这样,我需要把异常处理代码放在哪里呢?

目前,我看不到任何一个点,我可以把一个trycatch包起来,它可以捕获所有可能发生的异常。即使那样,我也会留下因为被抓而被处决的东西。或者我在想一个非常错误的方向?

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

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

我知道盲目忽略某些类型的错误是危险的,可能会损坏我的应用程序实例。如前所述,这个项目对任何人来说都不是关键任务。没有一个头脑清醒的人会把人类文明的生存赌在它上面。它只是一个测试特定设计方法的小工具。软件工程。

对于应用程序的即时使用,异常情况下可能发生的事情不多:

  • 无异常处理–错误对话框和应用程序退出。实验必须重复进行,尽管可能与另一个主题有关。没有记录错误,这是不幸的。
  • 一般异常处理–捕获良性错误,不会造成伤害。这应该是从我们在开发过程中看到的所有错误判断出来的常见情况。忽略这类错误不应该有直接的后果;核心数据结构经过了足够好的测试,它们可以很容易地生存下来。
  • 一般异常处理–捕获严重错误,可能在稍后崩溃。这可能很少发生。到目前为止我们还没见过。无论如何,错误都会被记录下来,崩溃可能是不可避免的。所以这在概念上类似于第一种情况。但我们有一个堆栈跟踪。在大多数情况下,用户甚至不会注意到。

至于程序生成的实验数据:一个严重的错误最坏只会导致没有数据被记录。微小的改变改变实验结果的可能性不大。即使在这种情况下,如果结果看起来可疑,错误也会被记录下来;如果数据点是一个总的异常值,人们仍然可以丢弃它。

总而言之:是的,我认为我自己仍然至少部分地神志正常,而且我不认为全局异常处理例行程序会让程序运行变得非常糟糕。如前两次所述,这样的决定可能是有效的,这取决于申请。在这种情况下,这是一个有效的决定,而不是完全和完全胡说八道。对于任何其他应用程序,该决定可能看起来不同。但请不要指责我或其他参与该项目的人仅仅因为我们忽略了错误就可能炸毁世界。

旁注:该应用程序只有一个用户。它不像Windows或Office那样被数以百万计的用户使用,在这种情况下,让用户产生异常泡沫的成本一开始就非常不同。


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

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


AppDomain.UnhandledException事件

This event provides notification of uncaught exceptions. It allows the
application to log information about the exception before the system
default handler reports the exception to the user and terminates the
application.

1
2
3
4
5
6
7
8
9
10
11
12
   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);
   }

If the UnhandledException event is handled in the default application
domain, it is raised there for any unhandled exception in any thread,
no matter what application domain the thread started in. If the thread
started in an application domain that has an event handler for
UnhandledException, the event is raised in that application domain. If
that application domain is not the default application domain, and
there is also an event handler in the default application domain, the
event is raised in both application domains.

For example, suppose a thread starts in application domain"AD1",
calls a method in application domain"AD2", and from there calls a
method in application domain"AD3", where it throws an exception. The
first application domain in which the UnhandledException event can be
raised is"AD1". If that application domain is not the default
application domain, the event can also be raised in the default
application domain.


使用nlog的示例代码,该代码将捕获来自AppDomain中所有线程、UI调度程序线程和异步函数的异常:

App.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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");

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

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


除此之外,注意将EDOCX1(及其类似物)与

1
2
3
4
5
<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

app.config中,将阻止辅助线程异常关闭应用程序。


下面是使用NLog的完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);
        }        
    }


}

比如"vb's on error resume next?"听起来有点吓人。第一个建议是不要这样做。第二个建议是不要做,不要想。你需要更好地隔离你的错误。至于如何处理这个问题,这取决于代码的结构。如果您使用的是MVC或类似模式,那么这不应该太难,而且绝对不需要全局异常吞咽器。其次,寻找一个好的日志库,比如log4net或者使用跟踪。我们需要知道更多的细节,比如您所讨论的异常类型以及应用程序的哪些部分可能导致抛出异常。