我们有一个 WPF 应用程序,其中的一部分可能在运行时抛出异常。我想全局捕获任何未处理的异常并记录它们,否则继续执行程序,就好像什么都没发生一样(有点像 VB 的 On Error Resume Next
)。
这在 C# 中可能吗?如果是这样,我究竟需要把异常处理代码放在哪里?
目前,我看不到任何可以包裹 try
/catch
并且可以捕获所有可能发生的异常的点。即便如此,我也会留下任何因捕获而被执行的东西。还是我在这里的想法非常错误?
ETA:因为下面很多人指出:该应用程序不是用于控制核电站。如果它崩溃了,这没什么大不了的,但主要与 UI 相关的随机异常在使用它的上下文中是一种麻烦。有(并且可能仍然有)其中一些,因为它使用插件架构并且可以由其他人扩展(在这种情况下也是学生;所以没有经验丰富的开发人员能够编写完全无错误的代码)。
至于被捕获的异常:我将它们记录到一个日志文件中,包括完整的堆栈跟踪。这就是那个练习的重点。只是为了反驳那些将我比喻为 VB 的 OERN 的人。
我知道盲目地忽略某些类别的错误是危险的,并且可能会损坏我的应用程序实例。如前所述,该程序对任何人都不是关键任务。没有一个心智正常的人会把人类文明的生存押在它上面。它只是一个用于测试某些设计方法的小工具。软件工程。
对于应用程序的立即使用,异常不会发生很多事情:
无异常处理——错误对话框和应用程序退出。实验必须重复,虽然可能与另一个主题。没有记录错误,这是不幸的。
通用异常处理——良性错误被捕获,无害。这应该是我们在开发过程中看到的所有错误判断的常见情况。忽略此类错误不会立即产生后果;核心数据结构已经过足够好的测试,因此它们很容易经受住这一考验。
通用异常处理——严重错误被捕获,可能在以后崩溃。这可能很少发生。到目前为止,我们从未见过它。无论如何都会记录错误,并且崩溃可能是不可避免的。所以这在概念上与第一种情况相似。除了我们有一个堆栈跟踪。在大多数情况下,用户甚至不会注意到。
至于程序产生的实验数据: 一个严重的错误最多只会导致没有数据记录。微小的变化几乎不可能改变实验的结果。即使在这种情况下,如果结果看起来可疑,也会记录错误;如果它是一个完全异常值,人们仍然可以丢弃该数据点。
总结一下:是的,我认为自己至少部分理智,我不认为全局异常处理例程会使程序运行必然是完全邪恶的。如前所述,这样的决定可能是有效的,具体取决于应用程序。在这种情况下,它被认为是一个有效的决定,而不是完全的胡说八道。对于任何其他应用程序,该决定可能看起来不同。但是请不要仅仅因为我们忽略了错误就指责我或参与该项目的其他人可能会炸毁世界。
旁注:该应用程序只有一个用户。它不像 Windows 或 Office 那样被数以百万计的用户使用,在这些情况下,让用户产生异常的成本一开始就已经非常不同了。
On Error Resume Next
的类似 VB 的代码流在 C# 中是不可能的。在 Exception
(C# 没有“错误”)之后,您不能简单地使用下一条语句继续:执行将在 catch
块中继续 - 或在下面的答案中描述的事件处理程序之一中继续。
使用 Application.DispatcherUnhandledException Event
。请参阅 this question 了解摘要(请参阅 Drew Noakes' answer)。
请注意,仍有一些例外情况会阻止您的应用程序成功恢复,例如在您尝试保存到数据库时堆栈溢出、内存耗尽或网络连接丢失。
使用 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);
}
}
UnhandledException
处理程序捕获。我必须查看 Windows 事件日志查看器才能找到正在发生的事情......
UnhandledException
处设置 e.Handled = true;
,以使应用程序不会因 UI 异常而崩溃
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”。如果该应用程序域不是默认应用程序域,则也可以在默认应用程序域中引发事件。
除了其他人在这里提到的内容外,请注意将 Application.DispatcherUnhandledException
(及其 similars)与
<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="1" />
</runtime>
</configuration>
在 app.config
中将防止您的辅助线程异常关闭应用程序。
这是使用 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);
}
}
}
就像“VB 的错误恢复下一步?”这听起来有点吓人。第一个建议是不要这样做。第二个建议是不要这样做,不要考虑它。您需要更好地隔离故障。至于如何解决这个问题,这取决于您的代码结构。如果您使用的是 MVC 之类的模式,那么这应该不会太难,并且绝对不需要全局异常吞咽器。其次,寻找一个好的日志库,如 log4net 或使用跟踪。我们需要了解更多细节,例如您正在谈论的异常类型以及应用程序的哪些部分可能导致抛出异常。
不定期副业成功案例分享