Ответ 1
Это ошибка. Task.Run
под капотом называет Task<Task>.Factory.StartNew
. Эта внутренняя задача получает правильный статус неисправности. Задача об упаковке не является.
Вы можете обойти эту ошибку, позвонив
Task.Factory.StartNew(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Однако, вы потеряете другую функцию Task.Run
, которая распаковывается. См:
Task.Run vs Task.Factory.StartNew
Подробнее:
Здесь код Task.Run
, где вы видите, что он создает обертку UnwrapPromise
(которая происходит от Task<TResult>
:
public static Task Run(Func<Task> function, CancellationToken cancellationToken)
{
// Check arguments
if (function == null) throw new ArgumentNullException("function");
Contract.EndContractBlock();
cancellationToken.ThrowIfSourceDisposed();
// Short-circuit if we are given a pre-canceled token
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation(cancellationToken);
// Kick off initial Task, which will call the user-supplied function and yield a Task.
Task<Task> task1 = Task<Task>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// Create a promise-style Task to be used as a proxy for the operation
// Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1, lookForOce: true);
return promise;
}
Конструктор задачи, который он вызывает, не принимает маркер отмены (и, следовательно, он не знает о внутреннем марке отмены задачи). Обратите внимание, что вместо этого создается стандартная CancellationToken. Здесь он вызывает:
internal Task(object state, TaskCreationOptions creationOptions, bool promiseStyle)
{
Contract.Assert(promiseStyle, "Promise CTOR: promiseStyle was false");
// Check the creationOptions. We only allow the AttachedToParent option to be specified for promise tasks.
if ((creationOptions & ~TaskCreationOptions.AttachedToParent) != 0)
{
throw new ArgumentOutOfRangeException("creationOptions");
}
// m_parent is readonly, and so must be set in the constructor.
// Only set a parent if AttachedToParent is specified.
if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
m_parent = Task.InternalCurrent;
TaskConstructorCore(null, state, default(CancellationToken), creationOptions, InternalTaskOptions.PromiseTask, null);
}
Внешняя задача (UnwrapPromise
добавляет продолжение). Продолжение рассматривает внутреннюю задачу. В случае сбоя внутренней задачи он считает, что исключение OperationCanceledException указывает на отмену (независимо от соответствующего токена). Ниже находится UnwrapPromise<TResult>.TrySetFromTask
(ниже также находится стек вызовов, показывающий, где он вызван). Обратите внимание на состояние Faulted:
private bool TrySetFromTask(Task task, bool lookForOce)
{
Contract.Requires(task != null && task.IsCompleted, "TrySetFromTask: Expected task to have completed.");
bool result = false;
switch (task.Status)
{
case TaskStatus.Canceled:
result = TrySetCanceled(task.CancellationToken, task.GetCancellationExceptionDispatchInfo());
break;
case TaskStatus.Faulted:
var edis = task.GetExceptionDispatchInfos();
ExceptionDispatchInfo oceEdi;
OperationCanceledException oce;
if (lookForOce && edis.Count > 0 &&
(oceEdi = edis[0]) != null &&
(oce = oceEdi.SourceException as OperationCanceledException) != null)
{
result = TrySetCanceled(oce.CancellationToken, oceEdi);
}
else
{
result = TrySetException(edis);
}
break;
case TaskStatus.RanToCompletion:
var taskTResult = task as Task<TResult>;
result = TrySetResult(taskTResult != null ? taskTResult.Result : default(TResult));
break;
}
return result;
}
Стек вызовов:
mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetCanceled(System.Threading.CancellationToken tokenToRecord, object cancellationException) Line 645 C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.TrySetFromTask(System.Threading.Tasks.Task task, bool lookForOce) Line 6988 + 0x9f bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.ProcessCompletedOuterTask(System.Threading.Tasks.Task task) Line 6956 + 0xe bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.InvokeCore(System.Threading.Tasks.Task completingTask) Line 6910 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.Invoke(System.Threading.Tasks.Task completingTask) Line 6891 + 0x9 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations() Line 3571 C#
mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() Line 2323 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.FinishStageTwo() Line 2294 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.Finish(bool bUserDelegateExecuted) Line 2233 C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) Line 2785 + 0xc bytes C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Line 2728 C#
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Line 2664 + 0x7 bytes C#
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 829 C#
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Line 1170 + 0x5 bytes C#
Он замечает исключение OperationCanceledException и вызывает TrySetCanceled для перевода задачи в отмененное состояние.
В стороне:
Еще одно замечание: при запуске методов async
на самом деле нет способа зарегистрировать токен отмены с помощью метода async
. Таким образом, любое событие OperationCancelledException, которое встречается в асинхронных методах, считается отменой.
Видеть
Связать аннулирование с помощью задачи асинхронного метода