2010-06-09 79 views
9

我使用Application.ThreadException事件来处理和记录我的winforms应用程序中的意外的异常。非常奇怪的Application.ThreadException行为

现在,在我的应用程序的某个地方,我有以下代码(或等价的东西相当,但这种虚拟的代码足以重现我的问题):

  try 
      { 
       throw new NullReferenceException("test"); 
      } 
      catch (Exception ex) 
      { 
       throw new Exception("test2", ex); 
      } 

我清楚地期待我Application_ThreadException处理程序通过“test2”异常,但情况并非总是如此。通常,如果另一个线程将我的代码编组到UI中,我的处理程序会收到“测试”异常,就好像我没有抓到“测试”一样。

这里是一个简短的例子,再现这种行为。我省略了设计者的代码。

 static class Program 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); 
     //Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); // has no impact in this scenario, can be commented. 

     AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); 

     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new Form1()); 
    } 

     static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 
    { 
     //this handler is never called 
    } 

    static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) 
    { 
     Console.WriteLine(e.Exception.Message); 
    } 
} 

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     button1.Click+=new EventHandler(button1_Click); 
    } 

    protected override void OnLoad(EventArgs e) { 
    System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); 
    t.Start(); 
    } 


    private void button1_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      throw new NullReferenceException("test"); 
     } 
     catch (Exception ex) 
     { 
      throw new Exception("test2", ex); 
     } 
    } 

    void ThrowEx() 
    { 
     this.BeginInvoke(new EventHandler(button1_Click)); 
    } 
} 

我的计算机上运行此程序的输出是:

test 
... here I click button1 
test2 

我转载此.NET的2.0,3.5和4.0。有人有合理的解释吗?

回答

1

例外#1:InvokeBeginInvoke在创建窗口句柄之前无法在控件上调用。

所以,不要试图从构造函数调用。做到在OnLoad()

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     this.Load += new EventHandler(Form1_Load); 
     button1.Click += new EventHandler(button1_Click); 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 
     System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); 
     t.Start(); 
    } 

    ... 
} 
+0

更改了答案。 – 2010-06-09 15:35:01

+0

你的建议很有道理,但这并不能改变这种奇怪的行为。 – Brann 2010-06-09 15:37:36

+0

@Brann,你是对的。奇怪的行为依然存在。但是,我已经通过在按钮点击处理程序中不执行catch(Exception ex)来纠正它,而只是“catch”。很有意思。不会抛出Exception和Exception的异常。 – 2010-06-09 15:40:43

0

你必须调用

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

首先在您的Main()方法中。

+0

我试过了,没有成功。 – Brann 2010-06-09 15:41:57

7

在代码中存在一个错误,使得很难调试发生了什么:在表单的Handle创建之前启动线程。这将使BeginInvoke失败。修复:

protected override void OnLoad(EventArgs e) { 
     System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx)); 
     t.Start(); 
    } 

Anyhoo,这是设计的行为。在运行的BeginInvoke目标Windows窗体的代码如下所示:

try 
    { 
     this.InvokeMarshaledCallback(tme); 
    } 
    catch (Exception exception) 
    { 
     tme.exception = exception.GetBaseException(); 
    } 
    ... 
     if ((!NativeWindow.WndProcShouldBeDebuggable && (tme.exception != null)) && !tme.synchronous) 
     { 
      Application.OnThreadException(tme.exception); 
     } 

它是exception.GetBaseException()调用搞砸了你的异常信息。为什么Windows Forms设计师选择这样做对我来说不是很清楚,对Reference Source中的代码没有评论。我只能猜测,如果没有它,异常将更加难以调试,以防由Windows窗体管道代码而不是应用程序代码引发。不是一个很好的解释。

他们已经表示,他们won't fix it,也许你可以添加你的投票。不要期待你的希望。

解决方法是不设置InnerException。当然不是一个好选择。

+0

正是我在找的答案;谢谢。 – Brann 2010-06-09 15:54:27

+1

'GetBaseException'调用旨在从'Invoke'解包'TargetInvocationException'。 – SLaks 2010-06-09 15:59:36

+3

不是这样。 Control.BeginInvoke()与Delegate.BeginInvoke()完全不同。例如,它不会将异常封送回调用方。 – 2010-06-09 16:04:29