优雅地处理损坏的状态exception
与此问题相关,我想强制CLR让我的.NET 4.5.2应用程序捕获损坏的状态exception,其唯一目的是记录它们然后终止应用程序。 如果我在应用程序周围的几个地方catch (Exception ex)
,那么这样做的正确方法是什么?
因此,在我指定属性后,如果我理解正确,所有
catch (Exception ex)
处理程序将捕获像AccessViolationException
这样的exception并愉快地继续。
是的,我知道catch (Exception ex)
是一个坏主意™,但如果CLR至少将正确的堆栈跟踪放入事件日志中,我将非常乐意向客户解释他的服务器应用程序在凌晨1点快速失败并在夜间离线是一件好事。 但不幸的是,CLR将一个不相关的exception记录到事件日志中,然后关闭该过程,以便我无法找出实际发生的情况。
问题是,如何实现这一目标,流程广泛:
if the exception thrown is a Corrupted State Exception: - write the message to the log file - end the process
(更新)
换句话说,这可能适用于简单应用程序中的大多数exception:
[HandleProcessCorruptedStateExceptions] [SecurityCritical] static void Main() // main entry point { try { } catch (Exception ex) { // this will catch CSEs } }
但是,它不适用于:
- 未处理的应用程序域exception(即在非前台线程上抛出)
- Windows服务应用程序(没有实际的
Main
入口点)
所以似乎是使这项工作的唯一方法,在这种情况下,我不知道如何在记录CSE后失败?
而不是使用
,最好使用[HandleProcessCorruptedStateExceptions]
(和[SecurityCritical]
),如下所述:
https://msdn.microsoft.com/en-us/magazine/dd419661.aspx
接下来,您的Main
方法应如下所示:
[HandleProcessCorruptedStateExceptions, SecurityCritical] static void Main(string[] args) { try { ... } catch (Exception ex) { // Log the CSE. } }
但请注意,这不会捕获更严重的exception,如StackOverflowException
和ExecutionEngineException
。
finally
涉及的try
块也不会被执行:
对于其他未处理的appdomain例外,您可以使用:
-
AppDomain.CurrentDomain.UnhandledException
-
Application.Current.DispatcherUnhandledException
-
TaskScheduler.UnobservedTaskException
(当特定处理程序适合您的情况时,请搜索详细信息。例如, TaskScheduler.UnobservedTaskException
有点棘手。)
如果您无权访问Main
方法,则还可以标记AppDomainexception处理程序以捕获CSE:
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; ... [HandleProcessCorruptedStateExceptions, SecurityCritical] private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { // AccessViolationExceptions will get caught here but you cannot stop // the termination of the process if e.IsTerminating is true. }
最后一道防线可能是一个非托管的UnhandledExceptionFilter,如下所示:
[DllImport("kernel32"), SuppressUnmanagedCodeSecurity] private static extern int SetUnhandledExceptionFilter(Callback cb); // This has to be an own non generic delegate because generic delegates cannot be marshalled to unmanaged code. private delegate uint Callback(IntPtr ptrToExceptionInfo);
然后在流程开始的某个地方:
SetUnhandledExceptionFilter(ptrToExceptionInfo => { var errorCode = "0x" + Marshal.GetExceptionCode().ToString("x2"); ... return 1; });
您可以在此处找到有关可能的返回代码的更多信息:
https://msdn.microsoft.com/en-us/library/ms680634(VS.85).aspx
UnhandledExceptionFilter
一个“特性”是,如果连接了调试器,则不会调用它。 (至少在我的情况下没有WPF应用程序。)所以要注意这一点。
如果从上面设置所有适当的ExceptionHandler,则应记录可记录的所有exception。 对于更严重的exception(如StackOverflowException
和ExecutionEngineException
),您必须找到另一种方法,因为整个过程在它们发生后无法使用。 一种可能的方法可能是另一个监视主进程并记录任何致命错误的进程。
其他提示:
- 在
AppDomain.CurrentDomain.UnhandledException
您可以安全地将e.ExceptionObject
为Exception
而不必担心 – 至少如果您没有任何IL代码抛出除Exception
之外的其他对象: 为什么UnhandledExceptionEventArgs.Exception对象而不是例外? - 如果要取消Windows错误报告对话框,可以查看此处: 如何在程序崩溃时终止程序? (这应该只是unit testing失败而不是永远卡住)
- 如果您有一个包含多个调度程序的WPF应用程序,您还可以对其他调度程序使用
Dispatcher.UnhandledException
。
感谢@haindl指出你也可以使用[HandleProcessCorruptedStateExceptions]
1属性修饰处理程序方法,所以我做了一个小测试应用程序,只是为了确认事情是否真的按照预期工作。
1 注意:大多数答案表明我还应该包含[SecurityCritical]
属性,尽管在下面的测试中省略它并没有改变行为(单独[HandleProcessCorruptedStateExceptions]
似乎工作得很好)。 但是,我将保留以下两个属性,因为我假设所有这些人都知道他们在说什么。 这是一个学校的“从StackOverflow复制”模式的例子。
显然,这个想法是从app.config
删除
设置,即只允许我们的最外层(入门级)处理程序捕获exception,记录它,然后失败。 添加设置将允许您的应用继续,如果您在某个内部处理程序中捕获exception,这不是您想要的 :想法只是获取准确的exception信息然后悲惨地死。
我使用以下方法抛出exception:
static void DoSomeAccessViolation() { // if you have any questions about why this throws, // the answer is "42", of course var ptr = new IntPtr(42); Marshal.StructureToPtr(42, ptr, true); }
1.从Main
exception:
[SecurityCritical] [HandleProcessCorruptedStateExceptions] static void Main(string[] args) { try { DoSomeAccessViolation(); } catch (Exception ex) { // this will catch all CSEs in the main thread Log(ex); } }
2.捕获所有exception,包括后台线程/任务:
// no need to add attributes here static void Main(string[] args) { AppDomain.CurrentDomain.UnhandledException += UnhandledException; // throw on a background thread var t = new Task(DoSomeAccessViolation); t.Start(); t.Wait(); } // but it's important that this method is marked [SecurityCritical] [HandleProcessCorruptedStateExceptions] private static void UnhandledException(object sender, UnhandledExceptionEventArgs e) { // this will catch all unhandled exceptions, including CSEs Log(e.ExceptionObject as Exception); }
我建议只使用后一种方法,并从所有其他位置删除[HandleProcessCorruptedStateExceptions]
,以确保exception不会被错误地捕获。 即如果你在某处有一个try/catch
块并且抛出了一个AccessViolationException
,你希望CLR跳过catch
块并在结束应用程序之前传播到UnhandledException
。