gpt4 book ai didi

c# - Task.Wait 在 OperationCanceledException 情况下的意外行为

转载 作者:可可西里 更新时间:2023-11-01 09:04:07 28 4
gpt4 key购买 nike

考虑以下代码:

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 状态,因为它的 token 与异常的 token 不匹配(并且 IsCancellationRequestedfalse):

If the token's IsCancellationRequested property returns false or if the exception's token does not match the Task's token, the OperationCanceledException is treated like a normal exception, causing the Task to transition to the Faulted state.

当我使用 .NET 4.5.2 在控制台应用程序中启动此代码时,我得到处于 Canceled 状态的任务(聚合异常包含未知的 TaskCanceledExeption,而不是原始的)。并且原始异常的所有信息都丢失了(消息、内部异常、自定义数据)。

我还注意到,在 OperationCanceledException 情况下,Task.Wait 的行为不同于 await task

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

案例 12 产生几乎相同的结果(仅在 StackTrace 中不同),但是当我将异常更改为 OperationCanceledException,然后我得到非常不同的结果:一个未知的 TaskCanceledException 如果 3 没有原始数据,预期的 OpeartionCanceledException 如果 4 包含所有原始数据(消息等)。

所以问题是:MSDN 是否包含不正确的信息?还是 .NET 中的错误?或者也许只是我不明白什么?

最佳答案

这是一个错误。 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。这是它调用的 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 作为指示取消(不管匹配的 token )。下面是 UnwrapPromise<TResult>.TrySetFromTask (下面也是显示调用位置的调用堆栈)。注意故障状态:

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 注册取消 token 方法。因此,在异步方法中遇到的任何 OperationCancelledException 都被视为取消。看 Associate a CancellationToken with an async method's Task

关于c# - Task.Wait 在 OperationCanceledException 情况下的意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25488357/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com