来自 WPF 应用程序的异常报告

本文关键字:异常 报告 应用程序 WPF 来自 | 更新日期: 2023-09-27 17:57:01

在未处理的异常期间,我有没有办法捕获输出并在应用程序崩溃时显示错误报告对话框?

<小时 />我

的想法是在后台运行一个小程序,唯一的工作是监听主应用程序的异常退出,然后显示"报告"对话框,用户可以选择通过电子邮件将错误的输出发送给我。

不太确定如何实现这一点,或者这是否是正确的方法。

报告错误消息将是一项简单的任务,但我不知道如何捕获未经处理的异常的输出或获取退出代码(我假设程序在崩溃时会给出 0 以外的退出代码)。

来自 WPF 应用程序的异常报告

您最好的机会是在应用程序内部。有两个钩子:

  • AppDomain.UnhandledException是终极的"包罗万象"
  • Application.ThreadException 是表单线程中发生的异常的特定于 UI 的捕获全部

"捕获全部"的正确位置取决于您的应用程序语义,并且很难说您应该将其放在不了解应用程序的情况下。应用程序还需要设置Application.SetUnhandledExceptionMode

拥有外部看门狗不太有用,因为它无法提供任何有意义的信息,导致应用程序崩溃。当它检测到"意外"退出时(它如何知道是"意外"?)为时已晚,无法收集任何有用的信息。使用内部处理程序,您可以收集异常和堆栈,并将它们提交给像 bugcollect.com 这样的分析服务,然后您现在将首先了解发生了什么,以及它发生的频率以及哪些部署受到影响(发生的位置)。还有其他类似的服务,如 exceptioneer.com 或Windows错误报告(这需要您的代码由受信任的颁发机构证书(如Verisign)签名)。依靠服务来收集事件远远优于发送邮件,您不想醒来并在收件箱中找到 2k 事件电子邮件并开始筛选它们以了解发生了什么。

最后一个世界:不要重新发明轮子:已经有很多框架来收集和记录异常,比如log4net和elmah。

您可以使用 NBug 库(您也可以在此处使用 nuget 包以方便安装)。只需安装 NuGet 包并按如下所示进行配置:

NBug.Settings.Destination1 =
  "Type=Mail;From=me@mycompany.com;To=bugtracker@mycompany.com;SmtpServer=smtp.mycompany.com;";
AppDomain.CurrentDomain.UnhandledException += NBug.Handler.UnhandledException;
Application.Current.DispatcherUnhandledException += NBug.Handler.DispatcherUnhandledException;

现在,所有未处理的异常都将被很好地捕获并为您包装礼品,您将通过电子邮件获得异常的完整详细信息。使用配置器工具确保您的配置正确。

涉及AppDomain.UnhandledException事件的答案可能隐含地假设在 WPF 的 UI 线程上引发任何未经处理的异常。这意味着,虽然它们经常可以工作,但它们在形式上与其他线程上的操作一起使用并不安全。更可靠的选项是 Application.DispatcherUnhandledException ,WPF 为应用程序提供了跨主 UI 线程、后台 UI 线程和BackgroundWorker实例实现未经处理的异常的自定义报告。

AppDomain.UnhandledException的一个可能的故障点是报表对话框可能需要单线程单元 (STA),因为 WPF 和 Windows 窗体都是 STA。由于线程池线程默认为多线程单元,因此在实例化报告对话框失败并出现类似下面的异常时,Task.Run()ThreadPool.QueueUserWorkItem()IProgress<T>.Report() 或许多类似 API 下的异步操作将导致未经处理的异常。这会导致应用程序崩溃,而没有机会提示用户报告基础问题。

System.InvalidOperationException: The calling thread must be STA, because many UI components require this.
   at System.Windows.Input.InputManager..ctor()
   at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
   at System.Windows.Input.KeyboardNavigation..ctor()
   at System.Windows.FrameworkElement.FrameworkServices..ctor()
   at System.Windows.FrameworkElement.EnsureFrameworkServices()
   at System.Windows.FrameworkElement..ctor()
   at System.Windows.Controls.Control..ctor()
   at System.Windows.Window..ctor()

我的经验Application.DispatcherUnhandledException是它与 TPL 结合使用是健壮的,因为 Task 及其相关类有助于将异常传播回调用方。总结 TPL 的异常处理,Wait()await自动重新引发,使用其他同步方法的调用方应检查Task.Exception

但是,正如 Application.DispatcherUnhandledException 的文档所指出的,其他情况需要 WPF 的调用方实现异常传播。也许其中最常见的是WPF的Progress<T>,奇怪的是,它不支持从其IProgress<T>.Report()实现中传播异常,即使它的唯一目的是将进度信息从工作线程移回UI。一种解决方法是使用类似于以下示例的方法包装进度更新处理程序。这只是一个草图;更频繁地轮询 Exception 属性对于停止错误可能很有价值,IDisposable 的更强语义可能比End()更可取,并且处理积压更新并发失败的情况可能很有用。

public class ExceptionPropagatingProgress<TProgress>
{
    private readonly Action<TProgress> onProgressUpdateCore;
    private readonly IProgress<TProgress> progress;
    public Exception Exception { get; private set; }
    public ExceptionPropagatingProgress(Action<TProgress> handler)
    {
        this.Exception = null;
        this.onProgressUpdateCore = handler ?? throw new ArgumentNullException(nameof(handler));
        this.progress = new Progress<TProgress>(this.OnProgressUpdate);
    }
    public void End()
    {
        if (this.Exception != null)
        {
            throw new AggregateException(this.Exception);
        }
    }
    private void OnProgressUpdate(TProgress value)
    {
        try
        {
            this.onProgressUpdateCore(value);
        }
        catch (Exception exception)
        {
            lock (this.onProgressUpdateCore)
            {
                if (this.Exception == null)
                {
                    this.Exception = exception;
                }
                else
                {
                    this.Exception = new AggregateException(this.Exception, exception);
                }
            }
        }
    }
    public void QueueProgressUpdate(TProgress value)
    {
        if (this.Exception != null)
        {
            throw new AggregateException(this.Exception);
        }
        this.progress.Report(value);
    }
}

您应该处理Application.ThreadException事件。