我们有一个WPF应用程序,它的某些部分可能会在运行时抛出异常。我希望全局捕获任何未处理的异常并记录它们,但如果没有发生任何事情,则继续执行程序(有点像VB的On Error Resume Next)。

这在c#中可行吗?如果是这样,我究竟需要把异常处理代码放在哪里?

目前,我看不到任何单一的点,我可以包装一个尝试/捕捉周围,这将捕获所有可能发生的异常。即使那样,我也会因为捕获而留下任何已经执行的东西。还是我想错了方向?

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

至于捕捉到的异常:我会将它们记录到日志文件中,包括完整的堆栈跟踪。这就是练习的意义所在。只是为了对抗那些把我的类比和VB的OERN类比的人。

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

对于应用程序的即时使用,在异常上不会发生很多事情:

No exception handling – error dialog and application exit. Experiment has to be repeated, though likely with another subject. No errors have been logged, which is unfortunate. Generic exception handling – benign error trapped, no harm done. This should be the common case judged from all errors we were seeing during development. Ignoring this kind of errors should have no immediate consequences; the core data structures are tested well enough that they will easily survive this. Generic exception handling – serious error trapped, possibly crash at a later point. This may happen rarely. We've never seen it so far. The error is logged anyway and a crash might be inevitable. So this is conceptually similar to the very first case, except that we have a stack trace. And in the majority of cases the user won't even notice.

对于程序生成的实验数据:如果出现严重错误,最坏的结果是没有数据记录。细微的变化,改变实验的结果是非常不可能的。即使在这种情况下,如果结果可疑,错误也会被记录下来;如果它是一个完全的异常值,人们仍然可以抛弃这个数据点。

To summarize: Yes, I consider myself still at least partially sane and I don't consider a global exception handling routine which leaves the program running to be necessarily totally evil. As said twice before, such a decision might be valid, depending on the application. In this case it was judged a valid decision and not total and utter bullshit. For any other application that decision might look different. But please don't accuse me or the other people who worked on that project to potentially blow up the world just because we're ignoring errors.

旁注:该应用程序只有一个用户。它不像Windows或Office那样被数百万人使用,在这些地方,有异常的成本对用户来说已经非常不同了。


当前回答

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

其他回答

Like "VB's On Error Resume Next?" That sounds kind of scary. First recommendation is don't do it. Second recommendation is don't do it and don't think about it. You need to isolate your faults better. As to how to approach this problem, it depends on how you're code is structured. If you are using a pattern like MVC or the like then this shouldn't be too difficult and would definitely not require a global exception swallower. Secondly, look for a good logging library like log4net or use tracing. We'd need to know more details like what kinds of exceptions you're talking about and what parts of your application may result in exceptions being thrown.

应用程序域中。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);
   }

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:

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

使用应用程序。DispatcherUnhandledException事件。请看这个问题的总结(参见Drew Noakes的回答)。

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

下面是使用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);
        }        
    }


}