【问题标题】:Exception handling with multiple forms多种形式的异常处理
【发布时间】:2011-08-16 02:39:41
【问题描述】:

我看到了不同的行为,我在调试时与在运行已编译的 .exe 时捕获或未捕获异常。我有两种形式(Form1 和 Form2)。 Form1 上有一个按钮,它实例化并调用 Form2 上的 ShowDialog。 Form2 上有一个按钮,它故意产生除以零错误。当我调试时,Form1 中的 catch 块被命中。当我运行编译的 .exe 时,它​​没有被命中,而是出现一个消息框,指出“您的应用程序中发生了未处理的异常。如果您单击继续,应用程序将忽略此错误并尝试继续。如果您单击退出,应用程序将立即关闭...试图除以零”。我的问题是为什么在调试和运行 .exe 时会出现不同的行为?如果这是预期的行为,那么是否有必要在每个事件处理程序中放置 try/catch 块?杀戮似乎有点疯狂,不是吗?

这是 Form1 的代码。

public partial class Form1 : Form
{
    public Form1()
    {
            InitializeComponent();

    }

    private void button1_Click(object sender, EventArgs e)
    {
        try
        {
            Form2 f2 = new Form2();
            f2.ShowDialog();
        }
        catch(Exception eX)
        {
            MessageBox.Show( eX.ToString()); //This line hit when debugging only
        }
    }
}

这是 Form2 的代码:

public partial class Form2 : Form
{
    public Form2()
    {
            InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
            int x = 0;
            int y = 7 / x;

    }
}

【问题讨论】:

  • 是时候拜访 Skeeter 或 Gravell!

标签: c# exception-handling unhandled-exception


【解决方案1】:

是的,这是设计使然,并且与 Windows 窗体的工作方式密切相关。在 Winforms 应用程序中,代码运行以响应 Windows 发布到活动窗口的消息。每个本机 Windows 应用程序都包含一个消息循环来检测这些消息。 Winforms 管道确保您的事件处理程序之一响应运行; button1_Click 在您的示例代码中。

大多数 Winforms 控件都实现了自己的事件处理程序。例如,PictureBox 有一个 Paint 事件处理程序,可确保将其 Image 绘制到屏幕上。这一切都是自动完成的,您不必自己编写任何代码来完成这项工作。

然而,当这段代码抛出异常时存在一个问题,因为没有涉及到你自己的代码,所以你无法捕捉到这样的异常。换句话说,没有地方让你注入你自己的 try 块。所涉及的您自己的程序代码的最后一点是启动消息循环的代码。 Application.Run() 方法调用,通常在 Program.cs 中。或者,如果您显示一个对话框,则调用 Form.ShowDialog()。这些方法中的任何一个都会启动消息循环。在 Application.Run() 调用周围放置一个 try 块没有用,应用程序将在捕获异常后终止。

为了解决这个问题,Winforms 消息循环代码在分发事件的代码周围包含一个 try 块。它的catch子句显示你提到的对话框,它是由ThreadExceptionDialog类实现的。

直截了当地问:这个 catch 子句确实妨碍了您在调试时对代码问题进行故障排除。调试器只会在没有处理异常的 catch 块时停止。但是当你的代码抛出异常时,你会想在调试时知道它。前面提到的消息循环中的代码知道是否附加了调试器。如果是,它会在没有 try/catch 块的情况下分派事件。现在,当您的代码抛出异常时,没有 catch 子句来处理它,调试器将停止程序,让您有机会找出问题所在。

也许您现在明白了为什么您的程序会以这种方式运行。调试时,消息循环中的 catch 子句被禁用,从而使 Form1 代码中的 catch 子句有机会捕获异常。如果不这样做,消息循环 catch 子句会处理异常(通过显示对话框)并防止异常展开到 Form1 代码。

您可以通过调用 Application.SetUnhandledExceptionMode() 方法并传递 UnhandledExceptionMode.ThrowException 来完全阻止使用消息循环 catch 子句。在 Main() 方法中执行此操作,在 Application.Run() 调用之前。现在,您的程序的行为将与任何一种方式相同。

这通常不是一个坏主意,在异常对话框中为用户提供继续选项是一个值得怀疑的功能。在这种情况下,请为 AppDomain.UnhandledException 事件实现事件处理程序,以便至少对用户进行一些诊断。

【讨论】:

    【解决方案2】:

    我的行为和你一样。我不知道为什么会发生这种情况,但是假设表单中的事件生成的异常将出现在 ShowDialog() 调用的堆栈上似乎是一个坏主意。最好做到这两点:

    • 在 Form2 中的事件处理程序中捕获并处理异常,因为这样做是有意义的,并且当您可以对异常做一些有意义的事情时。
    • 为整个应用程序添加一个未处理的异常处理程序 (`Application_ThreadException`) 以捕获任何未处理的异常。

    更新:这里是堆栈跟踪。调试版本:

    System.DivideByZeroException: Attempted to divide by zero.
       at WindowsFormsApplication1.Form2.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form2.cs:line 27
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.RunDialog(Form form)
       at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)
       at System.Windows.Forms.Form.ShowDialog()
       at WindowsFormsApplication1.Form1.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form1.cs:line 45
    

    发布:

    System.DivideByZeroException: Attempted to divide by zero.
       at WindowsFormsApplication1.Form2.button1_Click(Object sender, EventArgs e) in ...\WindowsFormsApplication1\Form2.cs:line 27
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    

    请注意,System.Windows.Forms.Form.ShowDialog() 在发布模式下不在堆栈跟踪中,这就是您的try {} catch {} 什么都不做的原因。同样值得注意的是,在调试情况下,它使用NativeWindow.DebuggableCallback,它可能旨在通过不破坏堆栈来帮助调试,而在发布模式下使用NativeWindow.Callback

    【讨论】:

    • 有趣的是,如果我在调试模式下编译并运行 .exe,我仍然看不到消息框(未命中 catch 块)。因此,无论我是在 Release 还是 Debug 中,行为都是相同的......异常未处理。但是单步执行代码时,您遇到了 catch 块。我想 ShowDialog 与发生错误的线程并不真正在同一个线程上是有道理的。在我看来,您在调试时点击了该异常处理程序这一事实似乎是一个 Visual Studio 错误。
    • 是的,似乎是 Visual Studio 正在改变行为,尽管我不确定这是一个错误。它似乎是有意帮助调试的。但是,此功能会误导您认为可以从 ShowDialog 捕获异常,而实际上这似乎不起作用。
    猜你喜欢
    • 2021-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-12
    • 2018-08-09
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多