如何使用Control.Invoke()抛出一个不会被忽略的异常?

时间:2013-03-29 13:54:50

标签: c# multithreading winforms exception

动机

我在Task应用程序中使用Windows.Forms我希望使用TaskTask.ContinueWith()来处理Control.Invoke()引发的任何异常重新抛出主UI线程上的任何异常。

但是,如果我使用Control.Invoke(),我就无法注意到例外情况 - 但如果我使用Control.BeginInvoke()则会有效。

有谁知道为什么它不适用于Control.Invoke(),以及如何让它发挥作用?

解决方法

我目前正在使用Control.BeginInvoke()而不是使用Control.Invoke()

重现的步骤

环境:Windows 7 x64,Visual Studio 2012,编译为.Net 4(但.Net 4.5作为VS2012的一部分安装)。

(1)使用名为form1的表单创建默认的Windows窗体应用程序。

(2)在名为button1的表单上放置一个按钮,并为其添加名为button1_Click()的处理程序。

(3)按如下方式实施button1_Click()

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        Thread.Sleep(1000);
        this.BeginInvoke(new Action(() =>
        {
            throw new InvalidOperationException("TEST");
        }));
    });
}

(4)运行程序并单击按钮。一秒钟后,将显示一个异常对话框,如您所愿。

(5)现在将this.BeginInvoke更改为this.Invoke

(6)再次运行程序并单击按钮。现在异常被忽略了!

Invoke()BeginInvoke()都在指定的Control的UI线程上执行,所以我无法理解为什么在一种情况下忽略异常而在另一种情况下它不是忽略...

我猜这肯定与Control.Invoke()如果抛出异常将永远不会返回这一事实有关,但是我的大脑很痛苦试图解决为什么这意味着异常(显然)完全忽略。

1 个答案:

答案 0 :(得分:3)

这是设计使然,Invoke()以不同于BeginInvoke()的方式处理异常。它会将异常编组并重新抛出,以便您知道调用的方法失败。这不适用于BeginInvoke(),因为线程已经移动,因此它在UI线程上引发。下一个问题是Task类吞下异常,所以你永远不会看到它。

你正在努力做到这一点。如果您喜欢默认的异常对话框,那么只需使用它就可以引发异常:

this.BeginInvoke(new Action(() => {
    using (var dlg = new ThreadExceptionDialog(new InvalidOperationException("TEST"))) {
        dlg.ShowDialog();
        Environment.Exit(1);
    }