使用多个表单的异常处理

10 c# exception-handling unhandled-exception

我正在看到不同的行为,在我调试时遇到或未捕获异常,而不是在运行已编译的.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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是Form2的代码:

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 11

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

大多数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()方法来阻止消息循环catch子句的使用,并传递UnhandledExceptionMode.ThrowException.在Application.Run()调用之前,在Main()方法中执行此操作.现在你的程序将以相同的方式运行.

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


Mar*_*ers 4

我的行为和你一样。我不知道为什么会发生这种情况,但假设表单中的事件生成的异常将出现在 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
Run Code Online (Sandbox Code Playgroud)

发布:

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)
Run Code Online (Sandbox Code Playgroud)

请注意,System.Windows.Forms.Form.ShowDialog()在发布模式下,它不在堆栈跟踪中,这就是为什么您try {} catch {}不执行任何操作的原因。另外值得注意的是,在调试情况下,它使用的是 using NativeWindow.DebuggableCallback,大概是为了通过不破坏堆栈来帮助调试而设计的,而在发布模式下是使用NativeWindow.Callback