Exception handling with multiple forms

梦想与她 提交于 2019-12-02 21:03:22

I get the same behaviour as you. I don't know why this happens, but it seems to be a bad idea to assume that an exception generated from an event in a form will appear on the stack of the ShowDialog() call. It would be better to do these two things:

  • Catch and handle the exceptions in the event handlers in Form2 where it makes sense to do so, and when you can do something meaningful with the exception.
  • Add an unhandled exception handler (`Application_ThreadException`) for your entire application to catch any unhandled exceptions.

Update: Here are the stack traces. Debug version:

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

Release:

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)

Notice that System.Windows.Forms.Form.ShowDialog() is not in the stack trace in release mode, which is why your try {} catch {} does nothing. Also notable is that in the debug case it is using NativeWindow.DebuggableCallback which is presumably designed to help debugging by not breaking the stack, whereas in Release mode is uses NativeWindow.Callback.

Yes, this is by design and is closely associated with the way Windows Forms works. In a Winforms app, code runs in response to messages posted to the active window by Windows. Every native Windows app contains a message loop to detect these messages. The Winforms plumbing ensures one of your event handlers runs in response; button1_Click in your example code.

Most Winforms controls implement their own event handlers. A PictureBox for example has a Paint event handler that ensures its Image is drawn to the screen. This is all done automatically, you don't have to write any code yourself to make this work.

There is however a problem when this code throws an exception, there is no way for you to catch such an exception since none of your own code was involved. In other words, there is no place for you to inject your own try block. The very last bit of your own program's code that was involved is the code that got the message loop started. The Application.Run() method call, normally in Program.cs. Or the Form.ShowDialog() call if you display a dialog. Either of those methods start a message loop. Putting a try block around the Application.Run() call isn't useful, the app will terminate after catching an exception.

To combat this problem, the Winforms message loop code contains a try block around the code that dispatches an event. Its catch clause displays the dialog you mentioned, it is implemented by the ThreadExceptionDialog class.

Getting to the point of your question: this catch clause really gets in the way of troubleshooting problems with your code when you debug. The debugger will only stop on an exception when there is no catch block that handles the exception. But when your code throws an exception, you'll want to know about it when you debug. The previously mentioned code in the message loop is aware whether or not a debugger is attached. If it is, it dispatches events without the try/catch block. Now, when your code throws an exception, there is no catch clause to handle it and the debugger will stop the program, giving you a chance to find out what went wrong.

Perhaps you see now why your program behaves the way it does. When you debug, the catch clause in the message loop is disabled, giving the catch clause in the Form1 code a chance to catch the exception. When you don't, the message loop catch clause handles the exception (by displaying the dialog) and prevents the exception from unwinding to the Form1 code.

You can prevent the message loop catch clause from being used at all by calling the Application.SetUnhandledExceptionMode() method, passing UnhandledExceptionMode.ThrowException. Do so in the Main() method, before the Application.Run() call. Now your program will behave the same either way.

This is in general not a bad idea, giving the user the option to Continue in the exception dialog is a questionable feature. Do implement an event handler for AppDomain.UnhandledException event in that case so there's at least some diagnostic to the user.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!