防止从BeginInvoke抛出外部exception

我有一个Application.ThreadException的处理Application.ThreadException ,但我发现exception并不总是正确传递给它。 具体来说,如果我从BeginInvoke回调中抛出exception内部exception,我的ThreadException处理程序不会获得外部exception – 它只获取内部exception。

示例代码:

 public Form1() { InitializeComponent(); Application.ThreadException += (sender, e) => MessageBox.Show(e.Exception.ToString()); } private void button1_Click(object sender, EventArgs e) { var inner = new Exception("Inner"); var outer = new Exception("Outer", inner); //throw outer; BeginInvoke(new Action(() => { throw outer; })); } 

如果我取消注释throw outer; 单击该按钮,然后消息框显示外部exception(及其内部exception):

System.Exception:Outer —> System.Exception:Inner
—内部exception堆栈跟踪结束—
at WindowsFormsApplication1.Form1.button1_Click(Object sender,EventArgs e)在C:\ svn \ trunk \ Code Base \ Source.NET \ WindowsFormsApplication1 \ Form1.cs:第55行
在System.Windows.Forms.Control.OnClick(EventArgs e)
在System.Windows.Forms.Button.OnClick(EventArgs e)
在System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
在System.Windows.Forms.Control.WmMouseUp(Message&m,MouseButtons按钮,Int32单击)
在System.Windows.Forms.Control.WndProc(Message&m)
在System.Windows.Forms.ButtonBase.WndProc(Message&m)
在System.Windows.Forms.Button.WndProc(Message&m)
在System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message&m)
在System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message&m)
在System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd,Int32 msg,IntPtr wparam,IntPtr lparam)

但如果throw outer; 在上面的代码中,在BeginInvoke调用内部,然后ThreadException处理程序获取内部exception。 在调用ThreadException之前,外部exception被剥离,我得到的只是:

System.Exception:内部

(这里没有调用堆栈,因为inner永远不会被抛出。在一个更现实的例子中,我捕获了一个exception并将其包装为重新抛出,会有一个调用堆栈。)

如果我使用SynchronizationContext.Current.Post而不是BeginInvoke则会发生同样的事情:外部exception被剥离,并且ThreadException处理程序仅获取内部exception。

我尝试在外面包装更多层exception,以防它只是剥离最外层的exception,但它没有帮助:显然某处有一个循环在while (e.InnerException != null) e = e.InnerException;的行上做某事while (e.InnerException != null) e = e.InnerException;

我正在使用BeginInvoke因为我有一些代码需要抛出一个未处理的exception才能立即被ThreadException处理,但是这段代码位于调用堆栈上方的catch块内(具体来说,它位于Task的操作中,并且Task将捕获exception并阻止它传播)。 我正在尝试使用BeginInvoke延迟throw直到下一次在消息循环中处理消息时,我不再在该catch内部。 我不依赖于BeginInvoke的特定解决方案; 我只是想抛出一个未处理的exception。

即使我在其他人的catch -all中,我怎么能导致exception – 包括它的内部exception – 到达ThreadException

(由于程序集依赖性,我无法直接调用我的ThreadException -handler方法:处理程序被EXE的启动代码挂钩,而我当前的问题是在较低层的DLL中。)

我假设您在x64 Windows系统上看到此行为,这是x64 Windows的一个 – 相当未知 – 的实现细节。 在这里阅读它

本文详细介绍了如何通过应用一些修补程序来解决这个问题,该修补程序据称随Win7 SP1一起发布,但几个星期后我在Win7 SP1上遇到了这个问题。

此外,您可以附加到AppDomain.FirstChanceException事件 ,该事件使您可以在将每个exception传递给CLR进行处理之前访问它们

将exception传播到更高层的推荐方法(除了通过等待任务隐式重新抛出)是删除Task主体中的catch-all,而是使用Task.ContinueWith在Task上注册Fault continuation,指定TaskContinuationOptions.OnlyOnFaulted 。 如果您正在通过中间层并且无法访问任务,则可以在您自己的UnhandledException事件中进一步包装它以向上传递Exception对象。

一种方法是将内部exception引用放在自定义属性或Data字典中 – 即,将InnerException属性保留为null,并以其他方式携带引用。

当然,这需要建立某种可以在抛出代码和处理代码之间共享的约定。 最好的方法可能是在由两段代码引用的项目中定义带有自定义属性的自定义exception类。

示例代码(虽然它需要更多的注释来解释为什么它正在做它正在做的疯狂事情):

 public class ExceptionDecorator : Exception { public ExceptionDecorator(Exception exception) : base(exception.Message) { Exception = exception; } public Exception Exception { get; private set; } } // To throw an unhandled exception without losing its InnerException: BeginInvoke(new Action(() => { throw new ExceptionDecorator(outer); })); // In the ThreadException handler: private void OnUnhandledException(object sender, ThreadExceptionEventArgs e) { var exception = e.Exception; if (exception is ExceptionDecorator) exception = ((ExceptionDecorator) exception).Exception; // ... }