Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the exception throwing behavior of "ShowDialog" different if launched with the Visual Studio debugger attached?

Tags:

c#

winforms

Consider this: Form1 launches Form2 as a modal dialog with a call to "System.Windows.Forms.Form.ShowDialog". Form2 throws an Exception on the GUI thread.

If I ran this program from the Visual Studio debugger, I can catch this Exception at the call site in Form1 (which I did not expect!). If I didn't launch the program without the debugger attached, even if I attach the debugger later, I cannot catch the Exception from Form1 (this is more the behaviour I expect).

Why can I catch the Exception in Form1 when running under the debugger? Or, more to the point, Why does the presence of the Debugger change the exception-throwing behaviour of "ShowDialog"?

The following code is sufficient to demonstrate the issue.

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!");
        }
    }
}
like image 796
Boinst Avatar asked Jan 05 '23 09:01

Boinst


1 Answers

That is done to help you debug unhandled exceptions. ShowDialog() is special, that method does not return until you close the dialog. It becomes modal like that by starting another dispatcher loop, the exact equivalent to Application.Run().

Exceptions raised by code that is activated by the dispatcher loop (event handlers) are reported through the Application.UnhandledException event. That behavior seriously gets in the way of debugging code that is still unstable. The debugger would have no reason to step in and show you how the code bombed since the exception is actually handled. Handled by raising the event. So special rule, if a debugger is attached then the dispatcher loop won't catch the exception and raise the event.

So now your catch clause works. But only if you debug, it will not work without one and won't work on the user's machine.

You can change this behavior by modifying your Main() method, pass UnhandledExceptionMode.ThrowException to Application.SetUnhandledExceptionMode(). Which is in general a wise thing to do since the default event handler is rather lame. It leaves it up to the user to decide to keep the program running, the user almost never knows the correct selection. You want to write an event handler for AppDomain.CurrentDomain.UnhandledException so every unhandled exception is reported properly, including the ones raised in worker threads. Like this:

    [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);
    }

That catch clause deserves a special mention. You have to really not care that the dialog failed to use catch-em-all exception handling like that. Very confounding to the user, the window disappeared for no apparent reason. And quite possibly quite confounding to your program, you also catch the really nasty stuff. And beware the trouble you'll have debugging that exception. You'll need Debug > Windows > Exception Settings > tick "Common Language Runtime Exceptions" to force the debugger to stop when the exception is thrown.

like image 150
Hans Passant Avatar answered Jan 14 '23 14:01

Hans Passant