为什么在使用Visual Studio调试器时,"ShowDialog"的异常抛出行为会有所不同?

5
考虑下面的情况:Form1通过调用"System.Windows.Forms.Form.ShowDialog"以模态对话框的形式启动Form2,并在GUI线程上引发异常。如果我从Visual Studio调试器中运行此程序,则可以在Form1中的调用位置捕获此异常(这是我没有预料到的!)。如果我不使用附加调试器的方式运行程序,即使稍后附加调试器,我也无法从Form1中捕获异常(这更符合我的期望)。
为什么在调试器中运行时,我可以在Form1中捕获异常?或者更重要的问题是,为什么调试器的存在会改变“ShowDialog”的异常抛出行为?
以下代码足以证明此问题。
using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    public class Form1 : Form
    {
        public Form1()
        {
            this.MouseClick += this.OnMouseClick;
        }

        private void OnMouseClick(object sender, MouseEventArgs e)
        {
            try
            {
                var f2 = new Form2();
                f2.ShowDialog(this);
            }
            catch (System.Exception ex)
            {
                MessageBox.Show(this, ex.Message, "Exception Caught!",
                    MessageBoxButtons.OK, MessageBoxIcon.Information
                    );
            }
        }
    }

    public class Form2 : Form
    {
        public Form2()
        {
            this.MouseClick += this.OnMouseClick;
        }

        private void OnMouseClick(object sender, MouseEventArgs e)
        {
            throw new Exception("Hey! That hurts!");
        }
    }
}
2个回答

8
那是为了帮助您调试未处理的异常。ShowDialog()是特殊的,该方法不会返回,直到您关闭对话框。通过启动另一个调度程序循环,它变得模态,与Application.Run()完全相同。
由调度程序循环(事件处理程序)激活的代码引发的异常通过Application.UnhandledException事件报告。这种行为严重妨碍了调试仍不稳定的代码。调试器没有理由介入并显示代码如何崩溃,因为实际上已经处理了异常。通过引发事件来处理。所以有一个特殊规则,如果调试器已连接,则调度程序循环不会捕获异常并引发事件。
现在你的catch子句起作用了。但只有在调试时才有效,在用户的计算机上无效。
您可以通过修改Main()方法来更改此行为,将UnhandledExceptionMode.ThrowException传递给Application.SetUnhandledExceptionMode()。这通常是明智的事情,因为默认的事件处理程序相当糟糕。它让用户决定是否继续运行程序,用户几乎从不知道正确的选择。您需要为AppDomain.CurrentDomain.UnhandledException编写事件处理程序,以便正确报告每个未处理的异常,包括在工作线程中引发的异常。像这样:
    [STAThread]
    static void Main() {
        if (!System.Diagnostics.Debugger.IsAttached) {
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
        MessageBox.Show(e.ExceptionObject.ToString());
        // Workaround for Windows 10.0.10586 bug:
        AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
        Environment.Exit(1);
    }

那个 catch 代码块值得特别提一下。如果你使用了类似捕获所有异常的方式,那么你必须真正不关心对话框失败了。这对用户来说非常令人困惑,窗口消失了却没有明显的原因。对于程序本身也可能非常让人困惑,因为你还会捕捉到真正危险的错误。同时,请注意在调试这种异常时可能会遇到的问题。你需要单击 Debug > Windows > Exception Settings,然后勾选“Common Language Runtime Exceptions”,以强制调试器在抛出异常时停止。

我测试了这个解决方案,效果很好。非常感谢你详细的解释,现在非常清晰易懂。 - Boinst

1

一篇有趣的文章,读起来引人入胜。我认为它不太适用于这种情况,在“onload”期间没有抛出异常。 - Boinst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接