February 1, 2005
This one has me stumped. I'd swear this behaved differently prior to .NET 1.1 service pack 1 (and/or XP SP2), but I can't prove it. As reported by a CodeProject reader, you'll get the standard .NET crash dialog in a console app, even if you've registered an unhandled exception handler for your AppDomain. What gives? Why doesn't AppDomain.CurrentDomain.UnhandledException capture exceptions on the main thread of a .NET console application?
But don't take my word for it-- try it yourself. Use this sample (source code zip file) from John Robbins, co-founder of Wintellect. Or, paste the code from this MSDN article into a new console app and run it:
Dim cd As AppDomain = AppDomain.CurrentDomain
AddHandler cd.UnhandledException, AddressOf MyHandler
Throw New Exception("1")
Catch e As Exception
Console.WriteLine("Catch clause caught : " + e.Message)
Throw New Exception("2")
' Expected output:
' Catch clause caught : 1
' MyHandler caught : 2
Sub MyHandler(sender As Object, args As UnhandledExceptionEventArgs)
Dim e As Exception = DirectCast(args.ExceptionObject, Exception)
Console.WriteLine("MyHandler caught : " + e.Message)
At first I was concerned that installing VS.NET had somehow forced me into some kind of bizarre first-chance exception mode exclusive to console applications, but not so. The compiled .exe behaves in the same way on every machine I tried it on: I get the standard .NET crash dialog, then after I dismiss that, I get the unhandled exception handler I wanted in the first place. That's.. not exactly the order I had in mind.
There's a way to disable the .NET JIT debugging dialog, as described by Scott Hanselman. But that's an extreme "solution": it disables the crash dialog for all .NET apps. It's also treating the symptoms rather than the disease: why can't we catch unhandled exceptions in console apps any more? I'd swear this worked the last time I looked at it. And the MSDN sample code certainly implies that it's possible-- but good luck getting that sample to print the expected output.
So what am I missing here?
Posted by Jeff Atwood
Just to add another test environment, I tried running this in Mono and it worked fine.
You gotta be kidding me! I submitted a feedback from that MSDN page indicating that the sample.. uh.. doesn't work.
I just noticed your code formatter (I rarely leave bloglines, where it can't be seen for some reason) with the options like copying to the clipboard. That's really sweet. Where can I get that or is it your personal creation?
I just tried it, and you're right. This is one of the most obvious omissions on MS side, unless it's "by design" :)
I just tried it, not just a 1.1 SP1 problem, I just tried in on 1.0, 1.1 and 2.0 and all ran into the same problem.
I've never noticed it, as I typically place a try...catch around everything in the main anyway...
Thanks for testing this. So it's not just me. The weird thing is, I would swear, SWEAR it wasn't like this before.
But it looks like David is right-- the only "solution" is to wrap main in a Try..Catch. And that of course works.
Which begs the question: why does the MSDN code sample expect to work as printed? It's totally broken! It's certainly what I would expect to happen, eg, unhandled exceptions go to the unhandled exception handler.. and it's what I remember happening before. Hmm.
The unhandled exception handler works properly for winforms apps. Slightly different handler, though: Application.ThreadException. You only have problems on new threads you spawn, and to get those to work, all you need to do is add the same handler per-thread.
I generally add handlers for both, and I don't think I've ever seen any exception condition where both fire. It's usually one or the other.
Sorry for the confusion. This behavior is actually the design, though the design can be a little convoluted at times.
The first thing to understand is that the UnhandledException event is not an unhandled exception "handler". Registering for the event, contrary to what the documentation says :-(, does not cause unhandled exceptions to be handled. (Since then they wouldn't be unhandled, but I'll stop with the circular reasoning already...) The UnhandledException event simply notifies you that an exception has gone unhandled, in case you want to try to save state before your thread or application dies. FWIW, I have filed a bug to get the docs fixed.
Just to complicate things, in v1.0 and 1.1, an unhandled exception did not always mean that your application would die. If the unhandled exception occurred on anything other than the main thread or a thread that began its life in unmanaged code, the CLR ate the exception and allowed your app to keep going. This was generally evil, because what would often happen was, for example, that ThreadPool threads would silently die off, one by one, until your application wasn't actually doing any work. Figuring out the cause of this kind of failure was nearly impossible. This may be why Jeff thought it worked before...he just always saw crashes on non-main threads.
In v2.0, an unhandled exception on any thread will take down the application. We've found that it's tremendously easier to debug crashes than it is to debug hangs or the silent-stoppage-of-work problem described above.
BTW, on my 1.1 machine the example from MSDN does have the expected output; it's just that the second line doesn't show up until after you've attached a debugger (or not). In v2 we've flipped things around so that the UnhandledException event fires before the debugger attaches, which seems to be what most people expect.
CLR Exceptions PM
Jeff, I love you! (metaphorically speaking, of course)
Your comment on handling Application.ThreadException has saved me about one hundred hours of rewriting an application in VB6. There is an issue with the C# I'm writing in that the COM that is calling it is buggy. The error doesn't matter, technically, so I am okay with ignoring it. The trick is, I don't want the user to need to click Continue in a JIT window.
I am seriously indebted to you. I randomly found this entry via Google but since earlier this summer I've been reading your blog. Too bad I didn't start reading your blog back in February of this year instead!
Test if I can post? If so I have a possible solution...
You want global error handling to work.
You want multiple threads.
You can probably clean this code up more.
But... It works.
I needed similar:
Before main form displays, a warning form that user is connected to test (if they are) is displayed. This is done on a background thread.
We still want the global error handling to work after creating the thread.
The following looks dubious but accomplishes this.
' This will trap errors on main form even after
' launching other threads.
AddHandler Application.ThreadException, AddressOf Application_ThreadException
' Create another thread to display a popup.
' In my case I check and display warning if user
' Is connected to test.
' Now on Form 1 raise an error.
' It will be trapped by Global Handler.
Public Sub TestPopup()
Dim oMCDelegate As MulticastDelegate
Dim oThreadStatCol As Collection = New Collection
oMCDelegate = CType(oMCDelegate.Combine(New WaitCallback(AddressOf CType( _
oThreadStatCol.Item(1), LaunchPopup).launch), _
New WaitCallback(AddressOf ThreadFinished)), MulticastDelegate)
ThreadPool.UnsafeQueueUserWorkItem(CType(oMCDelegate, WaitCallback), oThreadStatCol.Item(1))
Public Sub ThreadFinished(ByVal State As Object)
Private Class LaunchPopup
Public Sub launch(ByVal State As Object)
Dim frm As New Form2
PM for CLR Exceptions. Nice title :-)