49

Let's say we have some code like this running in the separate thread:

private static void ThreadFunc() {
    ulong counter = 0;

    while (true) {

        try {
            Console.WriteLine( "{0}", counter++ );
        }
        catch (ThreadAbortException) {
            Console.WriteLine( "Abort!" );
        }

    }
}

When Thread.Abort() is called, is it possible that the exception is thrown outside of catch block?

nalply
  • 26,770
  • 15
  • 78
  • 101
Overdose
  • 1,470
  • 3
  • 16
  • 29

4 Answers4

76

Actually yes, a ThreadAbortException is special. Even if you handle it, it will be automatically re-thrown by the CLR at the end of the try/catch/finally. (As noted in the comments, it can be suppressed with ResetAbort but by that point the code smells like rotten fish.)

Not to mention even though there is no obvious executable code outside of your try/catch/finally, every iteration of the loop winds up outside of the scope for a small duration so the abort could occur outside of your try block.

Unless you are actually doing something in the catch block, I would just make a try/finally and don't worry about ThreadAbortException. There are much better ways of aborting a thread without using Thread.Abort which not only chaotically interrupts your code at an unpredictable point, it's also not guaranteed to work because if your thread is currently calling out to some unmanaged code, the thread will not abort until control returns to managed code.

It's much better to use some type of synchronization primitive such as a ManualResetEvent to act as a flag telling your thread when to exit. You could even use a boolean field for this purpose which is what the BackgroundWorker does.

Josh
  • 68,005
  • 14
  • 144
  • 156
  • 3
    You can stop it being re-thrown using Thread.ResetAbort() can't you? – Phil Haselden Jul 18 '13 at 07:15
  • 1
    @PhilHaselden Yes: ["ResetAbort cancels the request to abort, and prevents the ThreadAbortException from terminating the thread."](http://msdn.microsoft.com/en-us/library/system.threading.thread.resetabort.aspx) – talles Oct 09 '13 at 16:34
  • 3
    You're absolutely right that the code smells like rotten fish when you do this. However, [ASP.net uses the it to implement `Response.Redirect`](http://support.microsoft.com/kb/918181), and are of course reset using `Thread.ResetAbort`, so in that context they can happen. What doesn't help the situation is that the .NET framework [isn't hardened against asynchronous exceptions](http://goo.gl/FBKioJ) so even though ASP.net uses them, it's still a very bad idea. – Pieter van Ginkel Nov 22 '13 at 22:00
  • 2
    @PietervanGinkel, I think that the big difference is that `Response.Redirect` doesn't abort another thread asynchronously; it aborts *itself*. It's not really an asynchronous thread abort. That's why it's a safe approach. Besides, it doesn't have much of a choice. It somehow has to completely interrupt processing, unwind the stack and return the thread to the thread pool no matter what the client code looks like. This is pretty much the only way to do it. – Euro Micelli May 14 '15 at 17:01
  • How do I cause a thread to terminate that is currently invoking blocking `Thread.CurrentThread.Join` to [pump](https://stackoverflow.com/a/6198862/2862241) *COM* messages? – Anton Shepelev Jan 27 '21 at 15:42
  • One solution: use time-limited [`Thread.Join(Int32)`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.join?view=net-5.0#System_Threading_Thread_Join_System_Int32_) in a loop. – Anton Shepelev Jan 27 '21 at 15:47
22

Yes. I suspect that you're asking because thread interruptions only occur when a thread could otherwise block (or if it's already blocked) - e.g. for IO.

There's no such guarantee for abort. It can happen at any time, basically, although there are delay-abort regions such as constrained execution regions and catch/finally blocks, where the abort request is just remembered, and the thread aborted when it exits the region.

Synchronous thread aborts (i.e. aborting your own thread) is reasonably safe, but asynchronous aborts (aborting a different thread) are almost always a bad idea. Read "Concurrent Programming on Windows" by Joe Duffy for further information.

EDIT: As noted by Eric below, aborting another thread isn't guaranteed to actually have any effect either. Just to quote the comment:

I would have said that the thread is aborted if it exits the region, emphasizing that Thread.Abort is completely unreliable. A thread which being aborted because it is stuck in an infinite loop will not abort if the loop is in such a region. This is yet another reason why Thread.Abort is a bad idea; if you can't rely on the desired effect actually happening then why would you call the method?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 4
    Good answer Jon, my only comment is that I would have said that the thread is aborted *if* it exits the region, emphasizing that `Thread.Abort` is completely unreliable. A thread which being aborted because it is stuck in an infinite loop will not abort if the loop is in such a region. This is yet another reason why `Thread.Abort` is a bad idea; if you can't rely on the desired effect actually happening then why would you call the method? – Eric Lippert May 24 '13 at 13:55
7

Actually, ThreadAbortException is special in case it's thrown by CLR or Thread.Abort method. Compare output:

  • Slightly modified example (added Console.WriteLine) from Joe Duffy's "Concurrent Programming on Windows". It throws the exception by Thread.CurrentThread.Abort:
    
    try
    {
        try
        {
            Thread.CurrentThread.Abort();
        }
        catch (ThreadAbortException)
        {
            Console.WriteLine("First");
            //Try to swallow it.
        } //CLR automatically reraises the exception here .
    }
    catch (ThreadAbortException)
    {
        Console.WriteLine("Second");
        Thread.ResetAbort();
        //Try to swallow it again .
    } //The in - flight abort was reset , so it is not reraised again .
    
    
    
    Output:
    First
    Second
    
  • Modify previous example to use different approach of ThreadAbortException instance creation:
    
    try
    {
        try
        {
            // get non-public constructor
            var cstor = typeof(ThreadAbortException)
                .GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            // create ThreadAbortException instance
            ThreadAbortException ex = cstor.Invoke(null) as ThreadAbortException;
    
            // throw..
            throw ex;
        }
        catch (ThreadAbortException)
        {
            Console.WriteLine("First");
        } 
    }
    catch (ThreadAbortException)
    {
        Console.WriteLine("Second");
        Thread.ResetAbort();
    } 
    
    Output:
    First
    

It seems like Thread's methods call native code internally which makes raised exception specific.

Ilya Builuk
  • 2,189
  • 2
  • 16
  • 10
  • Yes, it is specific.. which is exactly what the MSDN says. You can trap the exception related to JustMyCode and then Thread.ResetAbort() which will handle it properly. The try/catch wraps for the thread.Abort() are not specific to the exception. Therefore, you don't need to wrap it since it is special. Your above model is wrong and makes no sense. You only need to wrap the delegate portion where the abort is being thrown from the thread in question only.. one time.. that's it! – Latency Mar 03 '17 at 09:34
4

I am not 100% what you are asking but I wanted to point out that you will never be able to swallow a ThreadAbortException:

When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block.

Are you asking if it is possible to catch a ThreadAbortException that is thrown in another thread here with a try/catch? If that is your question, then no, you cannot.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635