C# 多表单的异常处理

C# 多表单的异常处理,c#,exception-handling,unhandled-exception,C#,Exception Handling,Unhandled Exception,我在调试和运行编译的.exe时看到了不同的异常捕获或未捕获行为。我有两张表格(表格一和表格二)。Form1上有一个按钮,用于实例化和调用Form2上的ShowDialog。Form2上有一个按钮,故意产生一个被零除的错误。当我调试时,会点击Form1中的catch块。当我运行编译后的.exe时,它不会被点击,相反,我会看到一个消息框,上面写着“应用程序中发生了未处理的异常。如果单击“继续”,应用程序将忽略此错误并尝试继续。如果单击“退出”,应用程序将立即关闭…尝试除以零”。我的问题是,为什么调试

我在调试和运行编译的.exe时看到了不同的异常捕获或未捕获行为。我有两张表格(表格一和表格二)。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;

    }
}

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

  • 在Form2中的事件处理程序中捕获并处理异常,如果这样做有意义,并且可以对异常执行有意义的操作。
  • 为整个应用程序添加未处理的异常处理程序(`Application\u 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
是的,这是通过设计实现的,与Windows窗体的工作方式密切相关。在Winforms应用程序中,运行代码以响应Windows发布到活动窗口的消息。每个本机Windows应用程序都包含一个消息循环来检测这些消息。Winforms管道确保其中一个事件处理程序响应运行;按钮1\u单击示例代码

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

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

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

直截了当地回答问题:这个catch子句实际上妨碍了在调试时对代码中的问题进行故障排除。只有当没有处理异常的catch块时,调试器才会在异常时停止。但是当您的代码抛出异常时,您会希望在调试时了解它。消息循环中前面提到的代码知道是否附加了调试器。如果是,则在不使用try/catch块的情况下发送事件。现在,当您的代码抛出异常时,没有catch子句来处理它,调试器将停止该程序,从而使您有机会找出错误所在

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

通过调用Application.SetUnhandledExceptionMode()方法,传递UnhandledExceptionMode.ThroweException,可以阻止消息循环catch子句被使用。在调用Application.Run()之前,在Main()方法中执行此操作。现在,您的程序将以同样的方式运行


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

是时候调用Skeeter或Gravell了!有趣的是,如果我在调试模式下编译并运行.exe,我仍然看不到消息框(catch块没有命中)。因此,无论我是在发布还是调试中,行为都是一样的……异常是未处理的。但是一步一步地通过代码,你就遇到了问题。我想ShowDialog与发生错误的线程并不在同一个线程上是有道理的。在调试时点击异常处理程序这一事实在我看来似乎是Visual Studio的一个bug。是的,似乎是Visual Studio改变了行为,尽管我不确定这是否是一个bug。它似乎有意帮助调试。但是,此功能误导您,使您认为在fac中从ShowDialog捕获异常是可以的