c# – Task.Wait在OperationCanceledException的情况下出现意外行为

前端之家收集整理的这篇文章主要介绍了c# – Task.Wait在OperationCanceledException的情况下出现意外行为前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
考虑下列代码
  1. CancellationTokenSource cts0 = new CancellationTokenSource(),cts1 = new CancellationTokenSource();
  2. try
  3. {
  4. var task = Task.Run(() => { throw new OperationCanceledException("123",cts0.Token); },cts1.Token);
  5. task.Wait();
  6. }
  7. catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }

由于MSDN任务应处于故障状态,因为它的令牌不匹配异常的令牌(并且IsCancellationRequested为false):

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启动此代码时,我将在取消状态中获取任务(聚合异常包含未知的TaskCanceledExeption,而不是原始的).原始异常的所有信息都会丢失(消息,内部异常,自定义数据).

我也注意到Task.Wait的行为与OperationCanceledException的情况下的等待任务不同.

  1. try { Task.Run(() => { throw new InvalidOperationException("123"); }).Wait(); } // 1
  2. catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
  3.  
  4. try { await Task.Run(() => { throw new InvalidOperationException("123"); }); } // 2
  5. catch (InvalidOperationException ex) { Console.WriteLine(ex); }
  6.  
  7. try { Task.Run(() => { throw new OperationCanceledException("123"); }).Wait(); } // 3
  8. catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
  9.  
  10. try { await Task.Run(() => { throw new OperationCanceledException("123"); }); } // 4
  11. catch (OperationCanceledException ex) { Console.WriteLine(ex); }

情况1和2产生几乎相同的结果(仅在StackTrace中有所不同),但是当我将异常更改为OperationCanceledException时,我得到非常不同的结果:在没有原始数据的情况下为3的未知TaskCanceledException,以及在具有所有原始数据的情况下期望的OpeartionCanceledException (消息等).

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

解决方法

这是一个bug. Task.Run在引导下调用任务< Task> .Factory.StartNew.该内部任务正在获得正确的“故障状态”.包装任务不是.

你可以通过调用解决这个bug

  1. Task.Factory.StartNew(() => { throw new OperationCanceledException("123",cts1.Token,TaskCreationOptions.DenyChildAttach,TaskScheduler.Default);

虽然,您将丢失任务解除的另一个功能.看到:
Task.Run vs Task.Factory.StartNew

更多细节:

这里是Task.Run的代码,您可以看到它正在创建一个包装UnwrapPromise(它来自Task< TResult&gt ;:::

  1. public static Task Run(Func<Task> function,CancellationToken cancellationToken)
  2. {
  3. // Check arguments
  4. if (function == null) throw new ArgumentNullException("function");
  5. Contract.EndContractBlock();
  6.  
  7. cancellationToken.ThrowIfSourceDisposed();
  8.  
  9. // Short-circuit if we are given a pre-canceled token
  10. if (cancellationToken.IsCancellationRequested)
  11. return Task.FromCancellation(cancellationToken);
  12.  
  13. // Kick off initial Task,which will call the user-supplied function and yield a Task.
  14. Task<Task> task1 = Task<Task>.Factory.StartNew(function,cancellationToken,TaskScheduler.Default);
  15.  
  16. // Create a promise-style Task to be used as a proxy for the operation
  17. // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1,to support in-delegate cancellation.
  18. UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1,lookForOce: true);
  19.  
  20. return promise;
  21. }

调用的Task构造函数不会取消取消令牌(因此它不知道内部任务的取消令牌).请注意,它会创建一个默认的CancellationToken.这是它所调用的ctor:

  1. internal Task(object state,TaskCreationOptions creationOptions,bool promiseStyle)
  2. {
  3. Contract.Assert(promiseStyle,"Promise CTOR: promiseStyle was false");
  4.  
  5. // Check the creationOptions. We only allow the AttachedToParent option to be specified for promise tasks.
  6. if ((creationOptions & ~TaskCreationOptions.AttachedToParent) != 0)
  7. {
  8. throw new ArgumentOutOfRangeException("creationOptions");
  9. }
  10.  
  11. // m_parent is readonly,and so must be set in the constructor.
  12. // Only set a parent if AttachedToParent is specified.
  13. if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
  14. m_parent = Task.InternalCurrent;
  15.  
  16. TaskConstructorCore(null,state,default(CancellationToken),creationOptions,InternalTaskOptions.PromiseTask,null);
  17. }

外部任务(UnwrapPromise添加了一个延续).继续检查内部任务.在内部任务发生故障的情况下,它考虑发现一个OperationCanceledException作为指示取消(不管匹配的令牌).以下是UnwrapPromise< TResult> .TrySetFromTask(下面也是调用堆栈,显示它被调用的位置).注意故障状态:

  1. private bool TrySetFromTask(Task task,bool lookForOce)
  2. {
  3. Contract.Requires(task != null && task.IsCompleted,"TrySetFromTask: Expected task to have completed.");
  4.  
  5. bool result = false;
  6. switch (task.Status)
  7. {
  8. case TaskStatus.Canceled:
  9. result = TrySetCanceled(task.CancellationToken,task.GetCancellationExceptionDispatchInfo());
  10. break;
  11.  
  12. case TaskStatus.Faulted:
  13. var edis = task.GetExceptionDispatchInfos();
  14. ExceptionDispatchInfo oceEdi;
  15. OperationCanceledException oce;
  16. if (lookForOce && edis.Count > 0 &&
  17. (oceEdi = edis[0]) != null &&
  18. (oce = oceEdi.SourceException as OperationCanceledException) != null)
  19. {
  20. result = TrySetCanceled(oce.CancellationToken,oceEdi);
  21. }
  22. else
  23. {
  24. result = TrySetException(edis);
  25. }
  26. break;
  27.  
  28. case TaskStatus.RanToCompletion:
  29. var taskTResult = task as Task<TResult>;
  30. result = TrySetResult(taskTResult != null ? taskTResult.Result : default(TResult));
  31. break;
  32. }
  33. return result;
  34. }

调用堆栈

  1. mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetCanceled(System.Threading.CancellationToken tokenToRecord,object cancellationException) Line 645 C#
  2. mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.TrySetFromTask(System.Threading.Tasks.Task task,bool lookForOce) Line 6988 + 0x9f bytes C#
  3. mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.ProcessCompletedOuterTask(System.Threading.Tasks.Task task) Line 6956 + 0xe bytes C#
  4. mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.InvokeCore(System.Threading.Tasks.Task completingTask) Line 6910 + 0x7 bytes C#
  5. mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.Invoke(System.Threading.Tasks.Task completingTask) Line 6891 + 0x9 bytes C#
  6. mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations() Line 3571 C#
  7. mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() Line 2323 + 0x7 bytes C#
  8. mscorlib.dll!System.Threading.Tasks.Task.FinishStageTwo() Line 2294 + 0x7 bytes C#
  9. mscorlib.dll!System.Threading.Tasks.Task.Finish(bool bUserDelegateExecuted) Line 2233 C#
  10. mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) Line 2785 + 0xc bytes C#
  11. mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Line 2728 C#
  12. mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Line 2664 + 0x7 bytes C#
  13. mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 829 C#
  14. mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Line 1170 + 0x5 bytes C#

它注意到OperationCanceledException,并调用TrySetCanceled将任务置于已取消状态.

放在一边

另外需要注意的是,当您开始使用异步方法时,没有一种方法可以使用异步方法注册取消令牌.因此,在异步方法中遇到的任何OperationCancelledException被认为是取消.
看到
Associate a CancellationToken with an async method’s Task

猜你在找的C#相关文章