Consider following scenario
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var startNew = Task.Factory.StartNew(() =>
{
var currentThread = Thread.CurrentThread;
try
{
using (cancellationTokenSource.Token.Register(currentThread.Abort))
new AutoResetEvent(false).WaitOne(Timeout.InfiniteTimeSpan);
}
catch (ThreadAbortException abortException)
{
throw new TimeoutException("Operation timeouted", abortException);
}
}, cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
startNew.ContinueWith(val => Console.WriteLine("Cancellation handled"), TaskContinuationOptions.OnlyOnCanceled);
startNew.ContinueWith(val => Console.WriteLine("Fault handled"), TaskContinuationOptions.OnlyOnFaulted);
startNew.ContinueWith(val => Console.WriteLine("Ran to completion handled"), TaskContinuationOptions.OnlyOnRanToCompletion);
Putting aside all the discussion that aborting threads is evil, why does this code doesn't make the task to go to Faulted state? However removing catch block or calling
Thread.ResetAbort()
seems to do the trick
It is about how thread abort works:
try {
try {
try {
Thread.CurrentThread.Abort();
} catch(Exception e) {
Console.WriteLine(e.GetType());
throw new Exception();
}
} catch(Exception e) {
Console.WriteLine(e.GetType());
}
} catch(Exception e) {
Console.WriteLine(e.GetType());
}
This code prints:
System.Threading.ThreadAbortException
System.Exception
System.Threading.ThreadAbortException
So, when your custom exception would be handled, ThreadAbortException
whould be re-thrown.
ThreadAbortException
is a special exception that can be caught by application code, but is re-thrown at the end of the catch
block unless ResetAbort
is called. MSDN
Now let us look to some source:
/// <summary>
/// Executes the task. This method will only be called once, and handles bookeeping associated with
/// self-replicating tasks, in addition to performing necessary exception marshaling.
/// </summary>
private void Execute()
{
if (IsSelfReplicatingRoot)
{
ExecuteSelfReplicating(this);
}
else
{
try
{
InnerInvoke();
}
catch (ThreadAbortException tae)
{
// Don't record the TAE or call FinishThreadAbortedTask for a child replica task --
// it's already been done downstream.
if (!IsChildReplica)
{
// Record this exception in the task's exception list
HandleException(tae);
// This is a ThreadAbortException and it will be rethrown from this catch clause, causing us to
// skip the regular Finish codepath. In order not to leave the task unfinished, we now call
// FinishThreadAbortedTask here.
FinishThreadAbortedTask(true, true);
}
}
catch (Exception exn)
{
// Record this exception in the task's exception list
HandleException(exn);
}
}
}
As you can see, there is special codepath for ThreadAbortException
case to transit task to the faulted state. As you hide ThreadAbortException
by TimeoutException
, that special codepath are not taken. So when regular codepath handle exception by recording it in the task's exception list, ThreadAbortException
would be re-thrown, which prevent correct task transition to the faulted state.