RunWorkerCompleted没有在主UI线程中执行?

时间:2010-10-22 14:36:20

标签: c# winforms exception .net-3.5 backgroundworker

BackgroundWorker事件中重新抛出异常时,我遇到了RunWorkerCompleted或.NET框架的奇怪行为。

后台线程中发生的异常传递给RunWorkerCompleted。在那里我做object temp = e.Result来重新抛出异常。由于这应该发生在主UI线程中,我希望异常传播到Application.Run(...),我用try-catch块包围。

在Visual Studio中运行应用程序时,这很好用。但是,当我在Visual Studio外部执行exe文件时,不会处理异常并使应用程序崩溃。

在主UI线程中执行RunWorkerCompleted事件 是不是

这些帖子不是重复的,而是确认我的情况很奇怪:

这是一个极端简化的代码示例,可以重现该错误:创建一个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框架“未处理的异常”消息。
  3. 有趣的是,如果我在2中点击“继续”,则应用程序仍然存在,并显示表单并接受用户输入。这必须意味着后台线程崩溃而不是主UI线程。主线仍然活着。

    (为什么我要做所有这些事情?我显示一个启动画面,当一些应用程序启动工作在后台完成时,启动画面通过标签和进度条显示进度。所以我有创建并显示启动画面,然后获取主UI线程以启动BackgroundWorker。在原始代码中,它将进度报告回更新启动表单的主线程。如果在启动期间发生异常,我想要在某些情况下,它们是具有特定含义的“业务例外”,例如“您无权使用此应用程序”。在这些情况下,在我让应用程序死亡之前,我会显示一个友好的消息框。一个finally块来清理资源。我不确定.NET框架“未处理的异常”对话框是否在杀死应用程序之前执行finally块。)

1 个答案:

答案 0 :(得分:4)

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

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

  Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

现在它的行为与调试器中的行为相同。这里更好的捕鼠器是为AppDomain.CurrentDomain.UnhandledException实现一个事件处理程序。这会捕获所有未处理的异常,包括在工作线程中引发的异常。并允许您调试未处理的异常,调试器在throw语句处停止。