2010-10-22 106 views
2

RunWorkerCompleted事件中重新抛出异常时,我遇到了BackgroundWorker或.NET框架的奇怪行为。RunWorkerCompleted未在主UI线程中执行?

发生在后台线程中的异常传递给RunWorkerCompleted。在那里我做了一个object temp = e.Result重新抛出异常。因为这应该发生在主UI线程中,所以我期望异常被传播到Application.Run(...),我用try-catch块包围它。

在Visual Studio中运行应用程序时,此工作正常。但是,当我在Visual Studio之外执行exe文件时,异常不会被处理并使应用程序崩溃。

难道RunWorkerCompleted事件是而不是在主UI线程中执行?

这些职位不重复,而是确认我的情况是怪异:

这是再现错误中有着非常简单的代码示例:创建WinForms项目并添加:

[STAThread] 
static void Main() 
{ 
    Application.EnableVisualStyles(); 
    Application.SetCompatibleTextRenderingDefault(false); 

    try 
    { 
     Form form = new Form(); 

     form.Load += delegate 
     { 
      BackgroundWorker bw = new BackgroundWorker(); 
      bw.DoWork += (sender, e) => 
      { 
       // do nothing 
      }; 
      bw.RunWorkerCompleted += (sender, e) => 
      { 
       throw new Exception("Booo!"); 
      }; 
      bw.RunWorkerAsync(); 
     }; 

     Application.Run(form); 
    } 
    catch (Exception ex) 
    { 
     while (ex is TargetInvocationException && ex.InnerException != null) 
      ex = ex.InnerException; 

     MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 
    } 
} 

然后尝试:

  1. 在Visual Studio中运行它。在应用程序退出之前,您会收到一个友好的消息框。
  2. 在Visual Studio外运行编译的可执行文件。你会得到一个.NET框架“未处理的异常”消息。

有趣的部分是,如果我在2中点击“继续”,应用程序仍然存在,并显示窗体并接受用户输入。这意味着不是主UI线程,后台线程崩溃了。主线程仍然活着。 (为什么我想要做所有这些事情?我显示一个启动画面,并且一些应用程序启动工作在后台完成时,启动画面通过标签和进度条显示进度。必须创建并显示启动画面,然后获取主UI线程以启动BackgroundWorker。在原始代码中,它将报告进度返回到更新启动窗体的主线程。如果在启动期间发生异常,我想要捕获它。在某些情况下,它们是“业务例外”,具有特定含义,例如“您无权使用此应用程序”。在这些情况下,我会在应用程序死前显示一个友好的消息框。此外,我有一个finally块清理资源我不确定.NET框架“未处理的异常”对话框是否执行finally b在杀死应用程序之前锁定)。

+0

你能制定一个问题吗? – 2010-10-22 14:49:49

+0

@Pieter:我做了,它是粗体。 – chiccodoro 2010-10-22 14:50:32

回答

4

Application.Run()中的消息循环代码周围有一个try/catch块,用于捕获由事件处理程序引发的未处理的异常。 catch子句引发了Application.ThreadException事件。该事件有一个默认处理程序,它显示一个ThreadExceptionDialog。给予用户忽略错误或中止程序的选项。

当您使用调试器运行程序时,此catch子句被禁用。这使您可以轻松调试异常。在禁用它的情况下,CLR将在您的Main()方法中找到catch子句。要禁用此行为,这行代码添加到您的Main()方法的顶部:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); 

现在的行为同样的方式在调试器中。这里更好的捕鼠器是为AppDomain.CurrentDomain.UnhandledException实现事件处理程序。这会捕获所有未处理的异常,包括在工作线程中引发的异常。并且让你调试未处理的异常,调试器停止在throw语句中。

+0

谢谢你这个富有洞察力的答案!我是否知道这是一种通常处理我的代码中的任何事件处理程序中发生的所有异常的方法?特别是处理按钮点击事件,菜单点击事件等的异常?到目前为止,我用try-catch块包围了每一个事件处理程序。这会不必要? – chiccodoro 2010-10-22 15:11:40

+0

'SetUnhandledExceptionMode'做到了。对于事件订阅方法,我将不得不多阅读一些内容才能理解如何使用它... – chiccodoro 2010-10-22 15:18:41

相关问题