提问人:Steinthor.palsson 提问时间:5/18/2011 最后编辑:ΩmegaManSteinthor.palsson 更新时间:2/28/2023 访问量:7224
来自 WPF 应用程序的异常报告
Exception reporting from a WPF Application
问:
在未经处理的异常期间,我有没有办法在应用程序崩溃时捕获输出并显示错误报告对话框?
我的想法是在后台运行一个小程序,唯一的工作是监听主应用程序的异常退出,然后显示“报告”对话框,用户可以选择通过电子邮件将错误的输出发送给我。
不太确定如何实现这一点,或者这是否是正确的方法。
报告错误消息将是一项简单的任务,但我不知道如何捕获未经处理的异常的输出或获取退出代码(我假设程序在崩溃时会给出 0 以外的退出代码)。
答:
您可以使用 NBug 库(也可以在此处使用 nuget 包以方便安装)。只需安装 NuGet 包并按如下方式进行配置:
NBug.Settings.Destination1 =
"Type=Mail;[email protected];[email protected];SmtpServer=smtp.mycompany.com;";
AppDomain.CurrentDomain.UnhandledException += NBug.Handler.UnhandledException;
Application.Current.DispatcherUnhandledException += NBug.Handler.DispatcherUnhandledException;
现在,所有未处理的异常都将被很好地捕获并为您包装,您将通过电子邮件获得异常的完整详细信息。使用配置器工具确保您的配置正确无误。
您应该处理该事件。Application.ThreadException
评论
您最好的机会是在应用程序内部。有两个钩子:
AppDomain.UnhandledException
是最终的“包罗万象”Application.ThreadException
是 Forms 线程中发生的异常的特定于 UI 的捕获所有捕获
“包罗万象”的正确位置取决于您的应用程序语义,并且很难说您应该在不了解您的应用程序的情况下将其放在哪里。应用程序还需要设置 Application.SetUnhandledExceptionMode
。
拥有外部看门狗的用处不大,因为它无法提供任何有意义的信息,为什么应用程序会崩溃。当它检测到“意外”退出时(它如何知道是“意外”?)已经太晚了,无法收集任何有用的信息。使用内部处理程序,您可以收集异常和堆栈,并将它们提交给像 bugcollect.com 这样的分析服务,然后您现在不仅可以了解发生了什么,还可以了解它发生的频率以及哪些部署受到影响(发生的位置)。还有其他类似的服务,如 exceptioneer.com 或 Windows 错误报告(此服务要求您的代码由受信任的权威证书(如 Verisign)签名)。依靠服务来收集事件远优于发送邮件,您不想醒来并在收件箱中找到 2k 起事件电子邮件并开始筛选它们以了解发生了什么。
最后一个世界:不要重新发明轮子:已经有许多框架可以收集和记录异常,比如 log4net 和 elmah。
评论
涉及 AppDomain.UnhandledException
事件的答案可能会隐式假设任何未经处理的异常都会在 WPF 的 UI 线程上引发。这意味着,虽然它们经常可以工作,但它们在形式上并不安全,不能安全地用于其他线程上的操作。更可靠的选项是 Application.DispatcherUnhandledException,WPF
为应用程序提供了该选项,用于跨主 UI 线程、后台 UI 线程和 BackgroundWorker
实例实现未经处理的异常的自定义报告。
一个可能的故障点是报表对话框可能需要单线程单元 (STA),因为 WPF 和 Windows 窗体都是 STA。由于线程池线程默认为多线程单元,因此 Task.Run
()、ThreadPool.QueueUserWorkItem()
、IProgress<T> 下的异步操作未处理异常。Report()
或许多类似的 API 将在实例化报告对话框失败时出现,并出现如下异常。这会导致应用程序崩溃,而没有机会提示用户报告基础问题。AppDomain.UnhandledException
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()
我的经验是,它与 TPL 结合使用是健壮的,因为其相关类有助于将异常传播回调用者。若要总结 TPL 的异常处理和自动重新引发,使用其他同步方法的调用方应检查 Task.Exception
。Application.DispatcherUnhandledException
Task
Wait()
await
但是,正如 的文档所指出的,其他情况需要 WPF 的调用方实现异常传播。其中最常见的可能是 WPF 的 Progress<T>
,奇怪的是,它缺乏对在其实现中传播异常的支持,即使它的唯一目的是将进度信息从工作线程移回 UI。一种解决方法是使用类似于以下示例的方法包装进度更新处理程序。这只是一个草图;更频繁地轮询属性对于停止错误可能很有价值,IDisposable
的更强语义可能比 更可取,并且以不同的方式处理积压更新失败的情况可能很有用。Application.DispatcherUnhandledException
IProgress<T>.Report()
Exception
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);
}
}
评论