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
0 comments