2

次のシナリオを検討してください

 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);

スレッドを中止することは悪いことだという議論はさておき、なぜこのコードはタスクを Faulted 状態にしないのでしょうか? ただし、catch ブロックまたは呼び出しを削除する

Thread.ResetAbort()

トリックを行うようです

4

1 に答える 1

3

スレッドの中止がどのように機能するかについてです。

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は、アプリケーション コードでキャッチできる特別な例外ですが、呼び出されcatchない限り、ブロックの最後で再スローされます。MSDNResetAbort

次に、いくつかのソースを見てみましょう:

/// <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タスクをフォルト状態に遷移させるための特別なコードパスがあります。で非表示ThreadAbortExceptionにするTimeoutExceptionと、その特別なコードパスは取得されません。そのため、通常のコードパスが例外をタスクの例外リストに記録して処理するThreadAbortExceptionと、再スローされ、障害状態への正しいタスク遷移が妨げられます。

于 2015-07-22T23:59:30.300 に答える