Have you tried putting a try / catch block in your main method?
The problem seems to be caused by the button event handler. If you throw an exception in DoRun()
- maybe after Form.Show()
- the exception will be caught as exspected no matter if you are runing inside Visual Studio or not.
Interesting is, that the behavior depends on whether the debugger is attached to the process, or not. Starting outside of Visual Studio and attaching the debugger later prevents the send feedback message box, detaching makes it occur again. The same from within Visual Studio - "Start without debugging" cause the send feedback message box to occur.
So I quickly steped through the framework source code after the exception occured in the button event handler and had a rough look at it - the message pump, the controls, and probably a lot of other code do a lot of stuff there. Because WinForms are just wrapper around native controls I assume that for some reason the exception is not returned to the same point or thread depending on whether a debugger is attached or not - maybe something goes wrong when the exception is passed across thread or process boundaries or something like that.
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.
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.
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.