views:

510

answers:

5

I have an idea why but I'd like to ask if someone has a good grasp on why the exception raised inside a thread is never caught by the code that started it. Here's some very simple code to demonstrate what I mean:

using System;
using System.Collections.Generic;
using System.Threading;

namespace TestCrash
{
    class Program
    {
        private static void Crash(object control)
        {
            AutoResetEvent are = (AutoResetEvent)(((object[])control)[0]);
            are.Set();
            throw new Exception("Burn baby burn");
        }
        static void Main(string[] args)
        {
            try
            {
                List<WaitHandle> waitHandles = new List<WaitHandle>();
                for (int i = 0; i < 100; i++)
                {
                    AutoResetEvent are = new AutoResetEvent(false);
                    waitHandles.Add(are);
                    object[] procControl = new object[] { are };
                    ThreadPool.QueueUserWorkItem(Crash, procControl);
                    WaitHandle.WaitAll(waitHandles.ToArray());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

I naively thought that by having the try/catch I would be safe, but I found out the hard way that it is not the case (it is crashing one of my services).

+15  A: 

Well in general, you've no idea where the originating thread will be by the time the exception is thrown in the new thread - why would it be waiting around for the thread to throw an exception?

Think of the stacks involved - when an exception is thrown, it goes up the stack until it reaches an appropriate catch block. The new thread has a completely separate stack to the creating thread, so it'll never reach the catch block in the creating thread's stack.

EDIT: Of course, you could design your system so that the creating thread did wait for other things to happen - a bit like the message loop in a Windows Forms application. The new thread could then catch the exception and send a message to the creating thread, which could then deal with the exception. That isn't the normal setup though - you have to do it all explicitly.

Jon Skeet
Thanks, Jon. What puzzled me was that it actually crashes the parent process; I would imagine that the thread should at least die peacefully instead of bringing everything down with it.
Otávio Décio
Nope - the point is that an unexpected exception may well indicate something being seriously wrong, and failing fast is generally a better idea. This is new behaviour as of .NET 2.0. See http://msdn.microsoft.com/en-us/library/ms228965.aspx
Jon Skeet
So I would conclude that any code block that is queued into a thread queue must be totally enclosed in a try/catch block itself, would that be a best practice? I have a service that I wouldn't want to come down just because one of its threads got in trouble...
Otávio Décio
Best practice is to catch and handle those exceptions you understand, and let those you do not take down the system lest you leave your data in an unknown state. That's why the 2.0 CLR kills your app if an unhandled exception is allowed to bubble past the top of the call stack.
Will
@Will - point taken.
Otávio Décio
+1  A: 

It's a bad idea to make assumptions, especially where multiple threads are involved (you know that old saying).

Why would the code that started the thread see the exception? The code that started the thread may not even exist when the exception is thrown.

John Saunders
as an aside, I hate that saying because it ignores the reality that we require assumptions to survive. The saying should refer to understanding what your assumptions are and determining where they are incorrect.
Nathan Koop
+1  A: 

The running thread will not be caught in your try/catch statement because it is running in another thread. Try/Catch only works for the current thread. What you need to do is have try/catch in the function being run by the thread, and have some way of managing what happens when that crash occurs.

Kibbee
+1  A: 

You might want to use an EventGeneratingThread wrapper - this will let you catch and deal with exceptions thrown in threads from the process that spawned them.

48klocs
+1  A: 

Try adding this before your DoWork Sub

<System.Diagnostics.DebuggerNonUserCodeAttribute()> _

I'm using the background worker, and all the Try Catch in my loop work as you'd expect them to with this.

Iggy