I guess you could say that every exception is special and important in its own way, but ThreadAbortException is really special. In what way, you ask? Well, let me tell you a story.
I wanted to implement a hard timeout for our system. That is, if a query to our service takes too long, force kill it, not matter what it is doing at the moment. You may claim that this is not a best practice, but it was the only way to ensure that even if a horrible bug that is causing an infinite loop in some very rare conditions creeped into our system, it won’t cause a meltdown in the server. So code like this was placed:
1: public T WithTimeout<T>(Func<T> action, int timeoutInMilliseconds )
2: {
3: using (var timer = new Timer(timeoutInMilliseconds))
4: {
5: var thread = Thread.CurrentThread;
6: timer.Elapsed += (o, s) => AbortThread(thread, timeoutInMilliseconds);
7: timer.Start();
8: try
9: {
10: return action();
11: }
12: catch (ThreadAbortException ex)
13: {
14: throw new ForcedTimeoutException(string.Format("Query had to be foricbly aborted after {0} milliseconds.", timeoutInMilliseconds), ex);
15: }
16: }
17: }
As you can see, I’m calling Thread.Abort in order to abort the work on the stuck thread, causing a ThreadAbortException to get thrown in it.
And this worked. Sort of. I wanted to write a test to make sure, so I wrote something like:
1: [Test]
2: public void Can_force_kill_query()
3: {
4: try
5: {
6: WithTimeout(() => Thread.Sleep(1000), 10);
7: }
8: catch (ForcedTimeoutException)
9: {
10: return;
11: }
12: Assert.Fail();
13: }
But I couldn’t get the test to pass! It seemed like ThreadAbortException keeps getting thrown, even though I’m catching it. Well, apparently, that’s what makes it special. ThreadAbortException will be thrown again and again by the CLR, even if you catch and “handle” it in a catch clause. You can overcome this behaviour, though, by calling Thread.ResetAbort. This puts things back to normal. So, the above catch clause was updated to:
1: catch (ThreadAbortException ex)
2: {
3: Thread.ResetAbort();
4: throw new ForcedTimeoutException(string.Format("Query had to be foricbly aborted after {0} milliseconds.", timeoutInMilliseconds), ex);
5: }
Hopefully you won’t ever need this, but just in case.