防止从BeginInvoke抛出外部异常

时间:2011-12-19 18:33:50

标签: c# exception unhandled-exception

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

示例代码:

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;行并单击按钮,则消息框会显示外部异常(及其内部异常):

  

System.Exception:Outer ---&gt; System.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(消息&amp; m,MouseButtons按钮,Int32点击)
    在System.Windows.Forms.Control.WndProc(Message&amp; m)
    在System.Windows.Forms.ButtonBase.WndProc(Message&amp; m)
    在System.Windows.Forms.Button.WndProc(Message&amp; m)
    在System.Windows.Forms.Control.ControlNativeWindow.OnMessage(消息&amp; m)
    在System.Windows.Forms.Control.ControlNativeWindow.WndProc(消息&amp; m)
    在System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd,Int32 msg,IntPtr wparam,IntPtr lparam)

但如果throw outer;位于BeginInvoke调用内,如上面的代码所示,则ThreadException处理程序获取内部异常。在调用ThreadException之前,外部异常被剥离,我得到的只有:

  

System.Exception:Inner

(这里没有调用堆栈,因为inner从未被抛出。在一个更现实的例子中,我抓住了一个异常并将其包装成重新抛出,会有一个调用堆栈。)< / em>的

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

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

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

即使我在其他人的ThreadException内,我怎样才能导致异常(包括其内部异常)到达catch

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

3 个答案:

答案 0 :(得分:0)

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

本文详细介绍了如何通过应用一些修补程序解决此问题,该修补程序据称随Win7 SP1一起提供,但几周前我在Win7 SP1上遇到了这个问题。

此外,您可以附加到AppDomain.FirstChanceException event,这可以让您在将每个例外传递给CLR进行处理之前访问

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

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

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

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;
    // ...
}