ThreadAbortException

ThreadAbortException

  • Comments 2

 

System.Threading.ThreadAbortException is just plain weird. For instance, most exceptions happen because of something code did on its own thread: even asynchronous exceptions are caused by something your code did on the current thread. (Note for the nitpickers: gamma rays can cause a bit in memory to flip randomly, bringing about unexpected program behavior. That's the source of most of the bugs in my own software.) But a ThreadAbortException is normally raised by action taken on an entirely different thread. This makes it a very unusual exception.

 

This example shows how one would normally raise a ThreadAbortException:

 

Imports System

Imports System.Threading

 

Module Module1

    Public Class ThreadTest

        Public Shared Sub Work()

            While True

                Console.Write(".")

            End While

        End Sub

    End Class

 

    Public Sub Main()

        Dim myThread As New Thread _

            (New ThreadStart(AddressOf ThreadTest.Work))

        myThread.Start()

        Thread.Sleep(42 * 10)

        myThread.Abort()

        myThread.Join()

    End Sub

End Module

 

Except for the call to myThread.Join most of this example is straightforward. The join just makes sure that the current thread doesn’t die until the one we’re trying to abort goes away. Note also that a thread could call abort on itself (Thread.CurrentThread.Abort) but that’s a little weird.

ThreadAbortException is automagically reraised

Chris Brumme noted that ThreadAbortException has the property of "undeniable exception propagation" but wisely declined to discuss the issue. Having this property means that the CLR will reraise ThreadAbortException at the end of any exception handler that handles the ThreadAbortException. (Exception handler refers to a catch block or the handler part of a filter—as opposed to the filter expression.) This means that normal exception handling constructs cannot deny the propagation of the exception: you can catch it all you like but it will just keep being raised.

 

This odd behavior adds a nice property to the action of aborting a thread. Thread Abort could have been written to just immediately stop the thread but then the thread would not have had the chance to clean up any native resources it might be holding.  But remember that the CLR already provides a way to clean up resources: fault and finally blocks. Having ThreadAbortException throw an exception instead of just aborting execution allows threads to clean up in a "normal" fashion.

 

If a ThreadAbortException were a normal exception, any catch block that handles it would stop the thread from aborting. And we know all too well that a lot of programmers catch exceptions they do not intend to handle. So the CLR re-raises the exception at the end of every catch block to make sure that the thread will continue to abort after having a chance to run its cleanup code.

 

But I want to live!

So if you want to stop a thread from aborting, how would you do so? You need to do so explicitly by calling a function that resets the exception: Thread.ResetAbort. If we modify Sub Work to reset the abort this thread will never stop printing dots (until a gamma ray hits your memory in just the right spot.) Here’s an example of how that works:

 

' Paste this into the code sample above

Public Class ThreadTest

Public Shared Sub Work()

Top:

       Try

            While True

                Console.Write(".")

           End While

        Catch e As ThreadAbortException

            Thread.ResetAbort()

       End Try

       GoTo Top

    End Sub

End Class

 

It should be apparent that ThreadAbortException is not a normal exception. So what does Thread.ResetAbort do, exactly? It turns out that the CLR does NOT simply raise a ThreadAbortException when Thread.Abort is called on a thread. Instead, it sets a bit on the thread's state--the AbortRequested bit. The execution engine checks this bit periodically as the thread winds down its execution. If this bit is set the runtime will raise a ThreadAbortException on the thread soon after Thread.Abort is called and again at the end of any handler that handles the ThreadAbortException.

 

Note that Thread.ResetAbort is a security-critical function so it can only be called by full-trust code. (In pre-v4 parlance, it performs a security demand.) But you normally don’t need to worry about your threads being aborted. AppDomain unload raises a ThreadAbortException on every thread in the domain. And hosted code (such as code running in ASP .net or SQL) may experience a thread abort or an AppDomain unload but generally you don’t have to worry about this.

 

If your code calls Thread.Abort, be aware that the call will block if the thread being aborted in a protected region—such as a catch block or a finally. If the thread calling Thread.Abort is holding a lock that the aborting thread needs you may deadlock. (Rude thread aborts will only block on CERs...see below for more details.)

Does Thread.ResetAbort handle the exception?

Thread.ResetAbort resets the state of the AbortRequested bit. It doesn’t actually have anything to do with the ThreadAbortException itself. Of course when the AbortRequested bit is not set then the runtime will not continue to reraise ThreadAbortException but the Thread.ResetAbort function doesn’t directly affect the exception.

 

In this code sample the thread abort is reset by a finally block inside of a nested try block. But finally blocks don’t handle exception so the ThreadAbortException should flow out of the finally block and be caught by the catch in the outer try. The ThreadAbortException won’t be reraised after this catch handler, however, because the AbortRequested bit is no longer set.

    ' Paste this into the code sample above

    Public Class ThreadTest

        Public Shared Sub Work()

            Try

                Try

                    ' Parent thread aborts this thread and the

                    ' abort happen inside of this try block

                    Thread.Sleep(1000)

                    Console.Write("Thread abort happens here")

                Finally

                    Thread.ResetAbort()

                End Try

            Catch e As ThreadAbortException

                ' This thread is no longer aborting but the

                ' finally block does not stop the exception.

                Console.Write("Caught a ThreadAbortException")

            End Try

        End Sub

End Class

 

Likewise, throwing a ThreadAbortException from user code (as opposed to calling Thread.Abort) will not set the ThreadAborting bit. The fact that there is a ThreadAbortException doesn’t necessarily mean that the thread is aborting.

Rude Thread Abort

What I've written so far has a pretty obvious problem: if your thread doesn't want to be aborted, it can just reset the abort. But what if you're the host and you really want the thread to be aborted? For example, SQL Server frequently aborts transactions and it doesn't care much what the code thinks about being aborted. The CLR hosting API allows a host to specify escalation policy about many things including how long to wait for threads to abort. After the thread has taken too long to abort (where “too long” is defined by the escalation policy) the CLR will escalate the thread abort into what we call a Rude Thread Abort. Rude thread aborts are handled a little differently: only EH constructs which are in methods contained in Constrained Execution Regions will get to see a Rude Thread Abort—no other cleanup code will execute.

 

For more information about CERs and rude thread aborts see Chris Brumme's blog entry on finalization, Brian Grunkemeyer’s post on the BCL team blog and this MSDN article on reliability. Most people--luckily--should not have to consider CERs and rude thread aborts in their code. If your code is hosted, trust your host and don't try to get around having your code aborted by your host.

Want to know more?

Chris Sells wrote an article about how ThreadAbortException is implemented using Rotor. And you can find a lot of information on the web about thread aborts: Thread.Abort is Sign of a Poorly Designed Program puts forth an interesting opinion, as does much of Joe Duffy’s blog. And if you have specific questions feel free to ask in the comments and I’ll answer them or find someone who can.

 

 

Andrew Pardoe,

PM, CLR.

Leave a Comment
  • Please add 8 and 5 and type the answer here:
  • Post
  • Actually I didn't understand the role of "call to myThread.Join()" method. Can you please come up with an example which shows the difference whether you call Join or not. Thanks for the article, didn't know that ThreadAbortException such a special exception.

  • @huseyint, the join is like it sounds, you are asking the thread to join the execution HERE once its complete.. technically this means that the thread the method is called on will block there until the other thread is finished.. otherwise the execution would continue as normal and carry on regardless of what state the other thread is in.

Page 1 of 1 (2 items)