Handling exceptions is not too difficult, if you know where to catch them. The problem are unhandled exceptions which might occur all over the application. Fortunately there is a way to catch (almost) all of them. Well, in most cases you won't be able to fix the problem and the program will terminate, but at least you can log them for further diagnosis.
Basically there are two types of exceptions:
- UI thread exceptions occur on WinForms threads and program termination can be avoided (in some cases probably shouldn't be)
- Non-UI thread exceptions occur on other threads and will always result in program termination
In order to catch these exceptions, it is necessary to attach custom event handlers as seen in the following code listing.
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
// UI thread exception handler
Application.ThreadException += new ThreadExceptionEventHandler(UIExceptionHandler);
// Non-UI thread exception handler
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(NonUIExceptionHandler);
Application.Run(new Form1());
}
private static void UIExceptionHandler(object sender, ThreadExceptionEventArgs t)
{
MessageBox.Show("UIExceptionHandler: " + t.Exception.Message);
}
private static void NonUIExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
MessageBox.Show("NonUIExceptionHandler: " + ex.Message);
}
Attaching the custom event handler should be done before calling Application.Run. Now to test these custom handlers, I added to buttons on the Form1 form. Button 1 raises an UI exception and button 2 raises a non-UI exception, because it is called from a separate thread.
Thread thread = null;
void ThreadMethod()
{
throw new Exception("This is a Non-UI Exception!");
}
private void button1_Click(object sender, EventArgs e)
{
throw new Exception("This is an UI Exception!");
}
private void button2_Click(object sender, EventArgs e)
{
ThreadStart threadStart = new ThreadStart(ThreadMethod);
thread = new Thread(threadStart);
thread.Start();
}
At the beginning of this post I already mentioned that there are exceptions to unhandled exceptions, which can not be handled. It works fine while working within the CLR, but when you call non-CLR functions (Win32 DLLs for instance), you might only catch low-detail exceptions or loose some. For testing purposes I created a Win32 Dll with C++, which exports the two methods (TestMethod and TestMethod2).
DWORD WINAPI runThread(LPVOID args)
{
throw new std::exception("Non-CLR Exception (Non-CLR Thread)");
}
void _stdcall TestMethod() {
throw new std::exception("Non-CLR Exception (CLR Thread)");
}
void _stdcall TestMethod2()
{
DWORD threadId;
int value = 10;
CreateThread(NULL, 0, runThread, &value, 0, &threadId);
}
If you speak C++, you can see that both methods raise exceptions. While TestMethod will execute on the same thread as the calling function, TestMethod2 raises the exception on a separate thread. Now back to the original C# program: I added the import statements and two additional buttons.
[DllImport("Win32Library.dll")]
protected static extern int TestMethod();
[DllImport("Win32Library.dll")]
protected static extern int TestMethod2();
private void button3_Click(object sender, EventArgs e)
{
TestMethod();
}
private void button4_Click(object sender, EventArgs e)
{
TestMethod2();
}
When I click button 3, my UIExceptionHandler catches the exception, but all I get is "UIExceptionHandler : External component has thrown an exception.". That means that the original exception type and message is lost. Even worse, when I click on button 4 , I get a "Microsoft Visual C++ Runtime Library - Runtime Error!" and the application terminates. None of the custom exception handlers is able to catch this exception and therefore it is lost.
When possible, I recommend to handle exceptions by try/catch blocks. But at least this should be mandatory when dealing with external (non-CLR) components. Full source code is available here.