Preventing Unhandled Exception Dialog Appearing

13,016

Solution 1

1.) I would recommend using the BackgroundWorker instead of separate threads like this. Your worker will catch exceptions and pass them along as a parameter to the complete handler.

2.) I would use ShowDialog() instead of Show() when displaying the second form, this will block the DoRun() at that method call and exceptions should then be caught by your surrounding try / catch (or the BackgroundWorker if you're using that instead).

I think the problem comes that since you're calling Show() you're essentially dispatching that call onto the Invoker, which ends up being queued in the UI thread. So when an exception happens there is nothing higher up the callstack to catch it. I believe calling ShowDialog() will fix this (and also allow you to drop that nasty for loop).

Something like this:

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

    private void button1_Click(object sender, EventArgs e)
    {
        // NOTE: I forget the event / method names, these are probably a little wrong.
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += (o, e) =>
        {
            Form2 f = new Form2();
            e.Result = f.ShowDialog();
        };
        worker.DoWorkComplete += (o, e) =>
        { 
            if(e.Error != null)
                MessageBox.Show(string.Format("Caught Error: {0}", ex.Message));

            // else success!
            // use e.Result to figure out the dialog closed result.
        };

        worker.DoWorkAsync();
    }
}

Actually, now that I think about it, it's sort of weird to be opening a dialog from a background thread but I think this will still work.

Solution 2

If you really want your second Form opened on a separate UI thread (not as ShowDialog()) to catch the exception and send it to your Application_ThreadException method, you need to ensure that the second thread is also set to CatchException and you need to subscribe to the Application.ThreadException on that thread, too. Both of these are thread-specific (and a bit quirky).

You can set the default "unhandled exception mode" by calling:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

This sets the application-wide mode to CatchException for any UI thread you create. (But this call will fail when running in the Visual Studio debugger and some other cases.) Or, your new UI thread can set its own mode with the usual call (same as passing true to this overload).

Either way, the new UI thread also needs to subscribe to the Application.ThreadException event itself, because the subscriber is stored in a [ThreadStatic] variable.

Application.ThreadException += Program.Application_ThreadException;

Or it could use a separate handler instead of routing to the same one, if that's helpful.

I'm not sure how this might intersect with using SafeThread to accomplish it, but I think if this is done correctly for the second UI thread it wouldn't be necessary to use SafeThread. It's much like you'd do it on your main UI thread.

Also see my answer to this question for more on the quirks of this stuff.

Solution 3

Instead of this line:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

You need this:

#if DEBUG
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
#else
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
#endif

This way, when you run the program in Visual Studio under Debug mode, Visual Studio will trap the exceptions when they happen so you can debug them at the point they occur. When you run your program in release mode, the exceptions will be caught by the handler for Application.ThreadException or the handler for the AppDomain.

This works perfectly in my program. I got tired of getting emails with the "Unhandled exception has occurred in your application..." box, so I implemented a universal form with a text box that allows me to dump specific information that I use to debug the problem.

Share:
13,016
rc1
Author by

rc1

Updated on June 28, 2022

Comments

  • rc1
    rc1 almost 2 years

    First let me say I have read this useful article thoroughly and am using the SafeThread class from CodeProject. I get the same result whether using Thread or SafeThread.

    I have reduced my problem down to an app consisting of two forms each with one button. The main program displays a form. When you click that button, a new thread starts which displays a second form. When you click the button on the second form, internally it just does "throw new Exception()"

    When I run this under VS2008, I see "Exception in DoRun()".

    When I run outside of VS2008, I get a dialog box "Unhandled exception has occurred in your application. If you click continue, the application ...."

    I have tried setting legacyUnhandledExceptionPolicy in the app.config to both 1 and 0.

    What do I need to do to capture the exception thrown in my second form, when not running under VS2008?

    Here's my Program.cs

        static class Program
        {
            [STAThread]
            static void Main()
            {
                Application.ThreadException += new ThreadExceptionEventHandler    (Application_ThreadException);
                Application.SetUnhandledExceptionMode    (UnhandledExceptionMode.CatchException);
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                try
                {
                   Application.Run(new Form1());
                }
                catch(Exception ex)
                {
                    MessageBox.Show("Main exception");
                }                
            }
    
            static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
            {
                MessageBox.Show("CurrentDomain_UnhandledException");
            }
    
            static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
            {
                MessageBox.Show("Application_ThreadException");
            }
        }
    

    Here's Form1:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            SafeThread t = new SafeThread(new SimpleDelegate(ThreadMain));
            try
            {
                t.ShouldReportThreadAbort = true;
                t.ThreadException += new ThreadThrewExceptionHandler(t_ThreadException);
                t.ThreadCompleted += new ThreadCompletedHandler(t_ThreadCompleted);
                t.Start();
            }
            catch(Exception ex)
            {
                MessageBox.Show(string.Format("Caught externally! {0}", ex.Message));
    
            }
        }
    
        void t_ThreadCompleted(SafeThread thrd, bool hadException, Exception ex)
        {
            MessageBox.Show("t_ThreadCompleted");
        }
    
        void t_ThreadException(SafeThread thrd, Exception ex)
        {
            MessageBox.Show(string.Format("Caught in safe thread! {0}", ex.Message));
        }
    
        void ThreadMain()
        {
            try
            {
                DoRun();
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Caught! {0}", ex.Message));
            }
        }
    
        private void DoRun()
        {
            try
            {
                Form2 f = new Form2();
                f.Show();
                while (!f.IsClosed)
                {
                    Thread.Sleep(1);
                    Application.DoEvents();
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show("Exception in DoRun()");
            }
        }
    }
    

    And here is Form2:

    public partial class Form2 : Form
    {
        public bool IsClosed { get; private set; }
    
        public Form2()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            throw new Exception("INTERNAL EXCEPTION");
        }
    
        protected override void OnClosed(EventArgs e)
        {
            IsClosed = true;
        }
    }
    
  • rc1
    rc1 almost 15 years
    Yes, it makes no difference (updated code listing to reflect this)
  • jasonh
    jasonh almost 15 years
    Yes, opening a dialog from a thread other than the main thread for the application is not only weird, but dangerous. It's just like changing the UI from a non-UI thread. It can and will blow up on you at run-time. If you need to show a dialog based on something in another thread, you need to send an event from that thread back to the UI thread, which can then show the dialog.
  • jasonh
    jasonh almost 15 years
    When I say "send an event back to the UI thread" I mean that the worker thread should somehow cause a form or control on the UI thread's Invoke or BeginInvoke method to be called. Exactly how you want to do this (I use an event and then the Form has a method that checks InvokeRequired before directly calling the method that shows the dialog or calling Invoke/BeginInvoke on that method) is up to you.
  • jasonh
    jasonh almost 15 years
    Why not? What happens? I have a working application (using plain old Threads) that, if an exception is thrown in the thread that isn't caught), the global handler takes it.
  • Rob Parker
    Rob Parker almost 15 years
    UnhandledExceptionMode.CatchException tells the message loop to catch exceptions that occur in processing a message and route them to Application.ThreadException (which WinForms handles itself if you don't subscribe). If they aren't caught there, they can unwind into unhandled exceptions which send AppDomain.UnhandledException and unload the AppDomain. But an attached debugger can intercept it and restore the original call stack (?) and possibly allow you to debug around it (if you can SetNextStatement to avoid it). Without a debugger you get the death dialog after the UE event returns.
  • Rob Parker
    Rob Parker almost 15 years
    Do you have the two lines backwards? Your text suggests that you meant for release mode to use Application.ThreadException, which requires the CatchException mode (only works for the UI thread). And the debugger would stop on the unhandled exception if you set the mode to ThrowException (if that's what you want). You also might want to key off of Debugger.IsAttached rather than #if DEBUG. Or, you could just have it CatchException and in Visual Studio: Debug->Exceptions... dialog check the box to break when CLR exceptions are Thrown (etc).
  • mili
    mili over 5 years
    Application.ThreadException += Program.Application_ThreadException; event handler works well, I put an event handler in Program.cs and it is firing when an un-handled exception ocured, thanks dude !!!