sstan
sstan

Reputation: 36523

Odd behavior: Catching ThreadAbortException and throwing different exception

As a result of investigating this question: Rethrowing exception in Task doesn't make the Task to go to faulted state, I noticed some very odd behavior with the ThreadAbortException that I can't make sense of.

Now, I know that ThreadAbortException is a very special kind of exception to begin with. And the documentation is pretty clear about that when it says:

ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block.

Scenario #1: Documented behavior.

static void Main(string[] args)
{
    try
    {
        Thread.CurrentThread.Abort();
    }
    catch (Exception tae)
    {
        Console.WriteLine("caught exception: " + tae.GetType().Name);
    }
    Console.WriteLine("will never be reached");
}

As expected, the ThreadAbortException is rethrown automatically, resulting in the following output:

caught exception: ThreadAbortException

Scenario #2: Where it gets interesting is when I decide to throw a different exception in the catch block:

static void Main(string[] args)
{
    try
    {
        Thread.CurrentThread.Abort();
    }
    catch (Exception tae)
    {
        Console.WriteLine("caught exception: " + tae.GetType().Name);
        throw new ApplicationException(); // will ThreadAbortException take precedence?
    }
    Console.WriteLine("will never be reached");
}

In this case, I assumed that, despite the throwing of the ApplicationException, that the ThreadAbortException would be rethrown anyways to ensure that the documented behavior was preserved. To my surprise, this is the output that resulted:

caught exception: ThreadAbortException

Unhandled Exception: System.ApplicationException: Error in the application.
   at ConsoleApplication1.Program.Main(String[] args) in C:\projects\ConsoleApplication1\Program.cs:line 193

Did the ApplicationException actually replace and prevent the ThreadAbortException from being thrown?!?

Scenario #3: And finally, to make matters more interesting, I wrap my existing exception handling with one more try-catch layer:

static void Main(string[] args)
{
    try
    {
        try
        {
            Thread.CurrentThread.Abort();
        }
        catch (Exception tae)
        {
            Console.WriteLine("caught exception: " + tae.GetType().Name);
            throw new ApplicationException();
        }
    }
    catch (Exception outerEx)
    {
        Console.WriteLine("caught outer exception: " + outerEx.GetType().Name);
    }
    Console.WriteLine("will never be reached");
}

Now I'm not too sure what to expect. Which exception will be caught in the outer catch block? Will it be ApplicationException? And if so, does that mean that I'll be able to swallow the exception and actually manage to print out the will never be reached string after all?

This is the actual output:

caught exception: ThreadAbortException
caught outer exception: ApplicationException

From the above output, it looks like the outer catch block does actually catch an ApplicationException. But by the end of that catch block, the ThreadAbortException now all of a sudden reappears out of thin air and gets rethrown?

Question(s): Can someone explain and reconcile scenarios #2 and #3? How is it that in scenario #2, it looks as though the ThreadAbortException is, unexpectedly, replaced by a different exception? Yet, in scenario #3, it looks like ThreadAbortException was still there all along? How is this happening? Is this behavior documented somewhere?

Upvotes: 3

Views: 1056

Answers (1)

jdphenix
jdphenix

Reputation: 15445

The behavior is implementation specific.

From Ecma-335 VI Annex E, Portability Considerations:

This clause gathers together information about areas where this Standard deliberately leaves leeway to implementations. This leeway is intended to allow compliant implementations to make choices that provide better performance or add value in other ways. But this leeway inherently makes programs non-portable. ...

and further (emphasis mine):

V I . E . 1 Uncontrollable Behavior

The following aspects of program behavior are implementation dependent. >Many of these items will be familiar to programmers used to writing code designed for >portability (for example, the fact that the CLI does not impose a minimum size for heap or stack).

  1. Size of heap and stack aren't required to have minimum sizes

Behavior relative to asynchronous exceptions (see System.Thread.Abort)

Globalization is not supported, so every implementation specifies its culture information including such user-visible features as sort order for strings.

Threads cannot be assumed to be either pre -emptively or non-pre-emptively scheduled. This decision is implementation specific.

Locating assemblies is an implementation-specific mechanism.

Security policy is an implemenation-specific mechanism.

File names are implementation-specific.

Timer resolution (granularity) is implementation -specific, although the unit is specified.

Upvotes: 1

Related Questions