为什么如果在附加Visual Studio调试器的情况下启动,"ShowDialog"的异常抛出行为会有所不同?

Boi*_*nst 4 c# winforms

考虑一下:Form1启动Form2作为模式对话框,调用"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!");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 6

这样做是为了帮助您调试未处理的异常.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);
    }
Run Code Online (Sandbox Code Playgroud)

该条款值得特别提及.你必须真的不在乎对话框没能像这样使用catch-em-all异常处理.非常混淆用户,窗口消失无缘无故.很可能你的程序很混乱,你也抓住了真正讨厌的东西.并注意你将调试该异常的麻烦.您需要Debug> Windows> Exception Settings>勾选"Common Language Runtime Exceptions"以强制调试器在抛出异常时停止.