.NET Exceptions – System.Threading.ThreadAbortException

Making our way through our detailed .NET Exception Handling series, today we’ll dig into the fun System.Threading.ThreadAbortException. A System.Threading.ThreadAbortException is thrown when the Abort() method is invoked on a Thread instance.

In this article we’ll go over the ThreadAbortException in more detail, examining where it resides in the .NET exception hierarchy, along with some functional sample code illustrating how System.Threading.ThreadAbortExceptions are commonly thrown, so let’s get to it!

The Technical Rundown

When Should You Use It?

To understand the purpose of the System.Threading.ThreadAbortException we first need to discuss threading in .NET and, specifically, how threads can be terminated. One simple technique of halting a thread is to call the Abort() method on the thread in question. Doing so will typically throw a ThreadAbortException and will then attempt to terminate the thread.

For example, here we create a new thread and pass it a lambda delegate method that just sleeps for one second before completing. After starting the thread we check the ThreadState, then call the Abort() method, which throws a System.Threading.ThreadAbortException and also causes the ThreadState to change to Aborted:

That said, invoking Abort() on a thread does not guarantee it will be terminated. One such scenario is if the thread contains a finally block with extensive code. Calling Abort() triggers such finally blocks, so an issue in such code could cause a lengthy delay in actual thread termination (or may never abort the thread at all).

One safety precaution is to call the Join method on the thread after Abort() is called. This temporarily blocks the calling thread (the thread in which the method was called) until the thread instance associated with the Join() has finished. Therefore, we can add thread.Join() to the end of our example above to ensure that the calling thread (Main thread, in most cases) is blocked until it has finished any final processing:

As you may notice by the timestamps, the invocation of Join() takes place almost instantaneously following the call to Abort(). This is because, even though our ThreadStart delegation method attempts to delay processing for one second, Abort() completes instantly because there’s no finally block holding things up.

To see how integrating a finally block might work we have the BasicThreadTester class. Its constructor method creates a new thread and sets its ThreadStart method to PerformSuspension. The PerformSuspension() method outputs a starting message, then contains a finally block where we’ve stuck our Thread.Sleep(1000) delay:

Instantiating this class results in the following output, which shows that the call to Abort() was properly delayed that extra one second necessary to execute the finally block code, before the thread aborted and then joined with the Main thread:

Now that we have a basic understanding of how aborting a thread works, let’s see how a System.Threading.ThreadAbortException might come up. We’ll start with the full code sample below, then break it down afterward to see what’s going on:

Our basic goal here is to use a singleton pattern — which you can learn all about in our Singleton Creation Design Pattern tutorial — to share a collection of Books between two threads. One thread will be actively destroying elements in the collection, while the other thread is outputting the changing Book count in the collection. While this is a simple example, it illustrates a basic structure that is commonly used to handle shared resources across multi-threaded applications.

We won’t go into much detail of the Utility.Singleton<T> or the Utility.Logging classes. The former just maintains a singleton instance of itself and implements some methods for manipulating an underlying collection of type T objects, while the later is used to output to the console.

Instead, we begin with the BookManager class, which instantiates a Singleton<Book> instance and implements two basic methods, DestroyBooks(int) and CountBooks(int, int):

DestroyBooks(int) loops through the Book collection of Singleton while it has values and outputs the result of Singleton.Pop(), which removes the last element of the collection. CountBooks(int, int) performs a loop — once for every iterations (default 5) and with a delay of milliseconds (default 500) — and outputs the current number of values in the Singleton Book collection.

As previously mentioned, we’ll be using two different threads to test these methods, aborting one midway through to see what happens. The AdvancedThreadTester class constructor performs all the required logic:

It starts by instantiating a new BookManager instance, then adding a small collection of Books to the underlying Singleton<Book> instance collection. Next, a new thread called Secondary is created, to which we assign bookManager.DestroyBooks() as the delegate method when the thread starts. Speaking of which, we then Start() the secondary thread and output a message. Then we call bookManager.CountBooks(), which is invoked on the Main thread (and was so-named elsewhere in the code).

This is where things become more interesting. Both DestroyBooks() and CountBooks() feature loops and intentional delays, so both threads are simultaneously processing for a few seconds, outputting their respective results. Eventually, CountBooks() completes execution and the Main thread moves onto the thread.Abort() call, which forces the Secondary thread to terminate itself before it finishes destroying all the books. As a safety precaution, we also Join() both threads together afterward.

The output from this code shows what’s going on using relative timestamps, and with indications of which thread is performing what task:

As we can see, the simultaneous processing worked as intended, allowing the Main thread to count the dwindling number of Books while the Secondary thread set out to destroy them. However, once the Main thread finished counting and invoked the Abort() method, our Secondary thread instantly threw a System.Threading.ThreadAbortException.

To get the most out of your own applications and to fully manage any and all .NET Exceptions, check out the Airbrake .NET Bug Handler, offering real-time alerts and instantaneous insight into what went wrong with your .NET code, along with built-in support for a variety of popular development integrations including: JIRA, GitHub, Bitbucket, and much more.