ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

目前,我看不到任何可以包裹 try/catch 并且可以捕获所有可能发生的异常的点。即便如此,我也会留下任何因捕获而被执行的东西。还是我在这里的想法非常错误?

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

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

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

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

无异常处理——错误对话框和应用程序退出。实验必须重复,虽然可能与另一个主题。没有记录错误,这是不幸的。

通用异常处理——良性错误被捕获,无害。这应该是我们在开发过程中看到的所有错误判断的常见情况。忽略此类错误不会立即产生后果;核心数据结构已经过足够好的测试,因此它们很容易经受住这一考验。

通用异常处理——严重错误被捕获,可能在以后崩溃。这可能很少发生。到目前为止,我们从未见过它。无论如何都会记录错误,并且崩溃可能是不可避免的。所以这在概念上与第一种情况相似。除了我们有一个堆栈跟踪。在大多数情况下,用户甚至不会注意到。

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

总结一下:是的,我认为自己至少部分理智,我不认为全局异常处理例程会使程序运行必然是完全邪恶的。如前所述,这样的决定可能是有效的,具体取决于应用程序。在这种情况下,它被认为是一个有效的决定,而不是完全的胡说八道。对于任何其他应用程序,该决定可能看起来不同。但是请不要仅仅因为我们忽略了错误就指责我或参与该项目的其他人可能会炸毁世界。

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

这并不是真正的想法——是的,您可能希望应用程序退出。但是,首先用它的 StackTrace 记录异常不是很好吗?如果您从用户那里得到的只是“当我按下此按钮时您的应用程序崩溃了”,您可能永远无法解决问题,因为您没有足够的信息。但是,如果您在更愉快地中止应用程序之前先记录异常,您将获得更多信息。
我现在在问题中详细阐述了这一点。我知道所涉及的风险,并且对于那个特定的应用程序,它被认为是可以接受的。并且在 UI 试图做一些漂亮的动画时,因为像索引越界这样简单的事情而中止应用程序是过度杀伤和不必要的。是的,我不知道确切的原因,但我们有数据支持绝大多数错误情况是良性的说法。我们掩盖的严重问题可能会导致应用程序崩溃,但无论如何如果没有全局异常处理就会发生这种情况。
另一个注意事项:如果您使用这种方法防止任何崩溃,用户很可能会喜欢它。
请参阅Windows Handling Unhandled Exceptions in WPF (The most complete collection of handlers) sample in C# for Visual Studio 2010。它有 5 个示例,包括 AppDomain.CurrentDomain.FirstChanceException、Application.DispatcherUnhandledException 和 AppDomain.CurrentDomain.UnhandledException。
我想补充一点,On Error Resume Next 的类似 VB 的代码流在 C# 中是不可能的。在 Exception(C# 没有“错误”)之后,您不能简单地使用下一条语句继续:执行将在 catch 块中继续 - 或在下面的答案中描述的事件处理程序之一中继续。

C
Community

使用 Application.DispatcherUnhandledException Event。请参阅 this question 了解摘要(请参阅 Drew Noakes' answer)。

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


如果您的异常发生在后台线程上(例如,使用 ThreadPool.QueueUserWorkItem),这将不起作用。
@PitiOngmongkolkul:处理程序被称为主循环中的事件。当事件处理程序返回时,您的应用程序将正常继续。
似乎我们需要设置 e.Handled = true,其中 e 是 DispatcherUnhandledExceptionEventArgs,以跳过退出程序的默认处理程序。 msdn.microsoft.com/en-us/library/…
@PitiOngmongkolkul 应该编辑答案以考虑到 e.Handled =true
这个解决方案本身可能不足以捕获所有异常。后台线程很可能会以非常糟糕的方式失败以使您的应用程序崩溃,我建议还使用 AppDomain.UnhandledException 或使用 LegacyUnhandledExceptionPolicy 实现 @charithj 解决方案...
H
Hüseyin Yağlı

使用 NLog 的示例代码将捕获从 AppDomain 中的所有线程、UI 调度程序线程和异步函数抛出的异常:

应用程序.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);
        }
    }

这是最完整的答案!包括任务调度程序异常。最适合我,干净简单的代码。
最近我遇到了一个客户的 App.config 损坏,她的 App 甚至没有启动,因为 NLog 试图从 App.config 中读取并抛出异常。由于该异常在静态记录器初始化程序中,因此它没有被 UnhandledException 处理程序捕获。我必须查看 Windows 事件日志查看器才能找到正在发生的事情......
我建议在 UnhandledException 处设置 e.Handled = true;,以使应用程序不会因 UI 异常而崩溃
C
CharithJ

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 属性。”
@GeorgeBirbilis:您可以在 App 构造函数中订阅 UnhandledException 事件。
V
Vadim Ovchinnikov

除了其他人在这里提到的内容外,请注意将 Application.DispatcherUnhandledException(及其 similars)与

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

app.config 中将防止您的辅助线程异常关闭应用程序。


N
NoWar

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


}

B
BobbyShaftoe

就像“VB 的错误恢复下一步?”这听起来有点吓人。第一个建议是不要这样做。第二个建议是不要这样做,不要考虑它。您需要更好地隔离故障。至于如何解决这个问题,这取决于您的代码结构。如果您使用的是 MVC 之类的模式,那么这应该不会太难,并且绝对不需要全局异常吞咽器。其次,寻找一个好的日志库,如 log4net 或使用跟踪。我们需要了解更多细节,例如您正在谈论的异常类型以及应用程序的哪些部分可能导致抛出异常。


关键是即使在未捕获的异常之后,我也希望保持应用程序运行。我只想记录它们(我们有一个自定义的记录框架,根据我们的需要)并且不需要仅仅因为某些插件在其用户交互代码中做了一些奇怪的事情而中止整个程序。从我到目前为止所看到的情况来看,干净地隔离原因并非易事,因为不同的部分并不真正了解彼此。
我理解,但您必须非常谨慎地在出现您未检查的异常后继续操作,可能会出现数据损坏等问题。
我同意 BobbyShaftoe。这不是正确的方法。让应用程序崩溃。如果故障出在某个插件上,请修复或找人修复插件。让它继续运行是非常危险的。您会得到奇怪的副作用,并且会达到无法为应用程序中发生的错误找到合乎逻辑的解释的地步。
我现在在问题中详细阐述了这一点。我知道所涉及的风险,并且对于那个特定的应用程序,它被认为是可以接受的。并且在 UI 试图做一些漂亮的动画时,因为像索引越界这样简单的事情而中止应用程序是过度杀伤和不必要的。是的,我不知道确切的原因,但我们有数据支持绝大多数错误情况是良性的说法。我们掩盖的严重问题可能会导致应用程序崩溃,但无论如何如果没有全局异常处理就会发生这种情况。
相反,您可以将每个插件放入自己的应用程序域中,这样您就可以捕获并记录其异常,然后让该域崩溃而不会导致整个应用程序崩溃。但它确实意味着更棘手的插件<=>应用程序通信。在大多数情况下,这些好处不值得额外的工作。