Reputation: 36523
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 thecatch
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
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).
- 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