在任务中重新抛出异常不会使任务进入故障状态
Rethrowing exception in Task doesn't make the Task to go to faulted state
考虑以下场景
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);
抛开所有关于中止线程是邪恶的讨论,为什么这段代码没有让任务进入故障状态?但是删除 catch 块或调用
Thread.ResetAbort()
似乎可以解决问题
关于线程中止的工作原理:
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());
}
此代码打印:
System.Threading.ThreadAbortException
System.Exception
System.Threading.ThreadAbortException
因此,当您的自定义异常被处理时,ThreadAbortException
将被重新抛出。
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
现在让我们看看一些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);
}
}
}
如您所见,ThreadAbortException
案例有特殊的代码路径将任务转移到故障状态。当您通过 TimeoutException
隐藏 ThreadAbortException
时,不会采用该特殊代码路径。因此,当常规代码路径通过将异常记录在任务的异常列表中来处理异常时,ThreadAbortException
将被重新抛出,这会阻止任务正确转换到故障状态。
考虑以下场景
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);
抛开所有关于中止线程是邪恶的讨论,为什么这段代码没有让任务进入故障状态?但是删除 catch 块或调用
Thread.ResetAbort()
似乎可以解决问题
关于线程中止的工作原理:
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());
}
此代码打印:
System.Threading.ThreadAbortException
System.Exception
System.Threading.ThreadAbortException
因此,当您的自定义异常被处理时,ThreadAbortException
将被重新抛出。
ThreadAbortException
is a special exception that can be caught by application code, but is re-thrown at the end of thecatch
block unlessResetAbort
is called. MSDN
现在让我们看看一些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);
}
}
}
如您所见,ThreadAbortException
案例有特殊的代码路径将任务转移到故障状态。当您通过 TimeoutException
隐藏 ThreadAbortException
时,不会采用该特殊代码路径。因此,当常规代码路径通过将异常记录在任务的异常列表中来处理异常时,ThreadAbortException
将被重新抛出,这会阻止任务正确转换到故障状态。