来自 WPF 应用程序的异常报告
Posted
技术标签:
【中文标题】来自 WPF 应用程序的异常报告【英文标题】:Exception reporting from a WPF Application 【发布时间】:2011-08-27 16:05:25 【问题描述】:假设我的应用程序中碰巧有一个未处理的异常,或者由于某种原因它崩溃了。 当应用程序崩溃时,我有什么方法可以捕获输出并显示错误报告对话框。
我在想的是在后台运行一个小程序,它唯一的工作就是监听主应用程序的异常退出,然后显示“报告”对话框,用户可以选择通过电子邮件将输出发送给我的错误。
不确定如何实现,或者这是否是正确的方法。
报告错误消息将是一件容易的事,但我不知道如何捕获未处理异常的输出或获取退出代码(我假设程序在崩溃时会给出除 0 以外的退出代码)。
如果能朝着正确的方向轻推就好了。
【问题讨论】:
【参考方案1】:您可以为此使用NBug 库(也可以使用nuget package here 来轻松安装)。只需安装 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;
现在所有未处理的异常都将被很好地捕获并为您打包,您将通过电子邮件获得异常的完整详细信息。使用配置器工具确保您的配置正确。
【讨论】:
【参考方案2】:您应该处理Application.ThreadException
事件。
【讨论】:
对于 WPF 应用程序不存在。【参考方案3】:您最好的机会是在应用程序中。有两个钩子:
AppDomain.UnhandledException
是终极“万能”
Application.ThreadException
是 UI 特定的包罗万象的表单线程中发生的异常
“包罗万象”的适当位置取决于您的应用程序语义,如果不了解您的应用程序,很难说应该把它放在哪里。应用程序还需要设置Application.SetUnhandledExceptionMode
。
外部看门狗的用处不大,因为它无法提供任何有意义的信息,为什么应用程序会崩溃。当它检测到“意外”退出时(它怎么知道是“意外”?)收集任何有用信息已经太晚了。使用内部处理程序,您可以收集异常和堆栈并将它们提交给像 bugcollect.com 这样的分析服务,然后您将在理解现在只有 发生了什么 以及 它发生的频率以及受影响的部署(发生的位置)。还有其他类似的服务,例如exceptioneer.com 或Windows Error Reporting(此服务要求您的代码由威瑞信等受信任的权威证书签署)。依靠服务收集事件远远优于发送邮件,您不想醒来并在收件箱中找到 2k 封事件电子邮件并开始筛选它们以了解 发生了什么。 p>
最后的世界:不要重新发明***:已经有很多框架可以收集和记录异常,例如 log4net 和 elmah。
【讨论】:
'catch-all' 是否会在所有线程上捕获异常,还是我会在每个线程上订阅它?我重新考虑了电子邮件方法,显然某种数据库会好得多。 :) 对于 WPF 应用程序,Application.ThreadException 不存在。您需要改为处理 DispatcherUnhandledException。【参考方案4】:涉及AppDomain.UnhandledException
事件的答案可能会隐含假设在 WPF 的 UI 线程上引发任何未处理的异常。这意味着,虽然它们经常工作,但它们在其他线程上的操作中使用起来并不安全。一个更可靠的选项是Application.DispatcherUnhandledException
,它为应用程序提供了跨主 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()
我对@987654341@ 的经验是,它与TPL 结合起来很健壮,就像Task
及其关联的类有助于将异常传播回调用者。总结the TPL's exception handling、Wait()
和await
自动重新抛出,使用其他同步方法的调用者应该检查Task.Exception
。
但是,正如Application.DispatcherUnhandledException
的文档指出的那样,其他情况需要 WPF 的调用者来实现异常传播。其中最常见的可能是 WPF 的 Progress<T>
,奇怪的是 lacks support for propagating exceptions 来自其对 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);
【讨论】:
以上是关于来自 WPF 应用程序的异常报告的主要内容,如果未能解决你的问题,请参考以下文章
处理来自 C++ DLL 的用户定义异常 - .NET PInvoke/Marshalling