考虑以下代码:
CancellationTokenSource cts0 = new CancellationTokenSource(), cts1 = new CancellationTokenSource();
try
{
var task = Task.Run(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token);
task.Wait();
}
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
由于MSDN任务应该处于Faulted
状态,因为它的令牌与异常令牌不匹配(IsCancellationRequested
也是false
):
如果令牌的IsCancellationRequested属性返回false或者异常的令牌与Task的令牌不匹配,则OperationCanceledException被视为正常异常,导致Task转换到Faulted状态。
当我使用.NET 4.5.2在控制台应用程序中启动此代码时,我得到Canceled
状态的任务(聚合异常包含未知TaskCanceledExeption
,而不是原始版本)。并且原始异常的所有信息都将丢失(消息,内部异常,自定义数据)。
我还注意到Task.Wait
的行为与await task
的{{1}}不同。
OperationCanceledException
案例try { Task.Run(() => { throw new InvalidOperationException("123"); }).Wait(); } // 1
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
try { await Task.Run(() => { throw new InvalidOperationException("123"); }); } // 2
catch (InvalidOperationException ex) { Console.WriteLine(ex); }
try { Task.Run(() => { throw new OperationCanceledException("123"); }).Wait(); } // 3
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
try { await Task.Run(() => { throw new OperationCanceledException("123"); }); } // 4
catch (OperationCanceledException ex) { Console.WriteLine(ex); }
和1
产生几乎相同的结果(仅在2
中有所不同),但是当我将异常更改为StackTrace
时,我会得到非常不同的结果:未知OperationCanceledException
,如果TaskCanceledException
没有原始数据,则3
以及OpeartionCanceledException
包含所有原始数据(消息等)。
所以问题是:MSDN是否包含不正确的信息?或者它是.NET中的错误?或者也许只是我不明白?
答案 0 :(得分:5)
这是一个错误。引擎Task.Run
呼叫Task<Task>.Factory.StartNew
。这个内部任务正在获得Faulted的正确状态。包装任务不是。
您可以通过调用
来解决此错误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;
}
它调用的Task构造函数不接受取消令牌(因此它不知道内部Task的取消令牌)。请注意,它会创建一个默认的CancellationToken。这是它所称的ctor:
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被视为取消。
看到
Associate a CancellationToken with an async method's Task
答案 1 :(得分:5)
Matt Smith - 谢谢你,你的解释非常有用。
阅读并测试一段时间后,我注意到原始问题并不完全正确。这不是Task.Wait
的问题。我可以使用Task.ContinueWith
获取此错误行为,检查第一个任务的Status
- 它是Canceled
。所以我相信最终的答案是:
如果您使用Task.Run
重载创建任务,将Func<Task>
或Func<Task<TResult>>
作为第一个参数,并且您的代理会抛出OperationCanceledException
,并且如果您使用对于返回的任务,Task.Wait
或Task.ContinueWith
,由于.NET中的错误(如 Matt Smith 所解释的那样),您将丢失包含所有数据的原始异常,并使任务变得不正确{ {1}}状态而不是Canceled
,无论是否匹配documented logic。
所有这些条件都很重要。如果您在创建的任务上使用Faulted
- 它可以正常工作。如果您使用以await
或Task.Run
作为第一个参数的Action
重载 - 它在所有情况下都能正常工作(Wait,ContinueWith,await)。
我还注意到重载方法选择逻辑的奇怪行为。我写的时候
Func<TResult>
我希望使用Task.Run(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token);
重载,这不会被破坏。但不知何故,似乎使用了破碎的Task.Run(Action, CancellationToken)
。所以我被迫做这样的事情
Task.Run(Func<Task>, CancellationToken)
或使用Task.Run((Action)(() => { throw new OperationCanceledException("123", cts0.Token); }), cts1.Token);
。
答案 2 :(得分:4)
这种行为同时非常有趣和奇怪。
正如其名称所示,AggregateException
的目的是将执行应用程序期间发生的多个异常/错误组合在一起。因此,在第三种情况下,您有一个OperationCanceledException
作为内部异常,AggregateException
的堆栈跟踪应该报告所有相关内容,包括数据(如123
),因为它是如下所示,在第四种情况下:
关于您的问题:
MSDN是否包含不正确的信息?
它应该始终报告有关类,方法等行为的正确和准确的信息。
或者它是.NET中的错误吗?
最有可能的是,这是一个错误。这种情况发生的原因是不可理解的。 Here您会找到有关此问题的相关问题。请report向Microsoft提出此问题。