抛出OperationCanceledException时,任务行为与Task <t>行为不同

时间:2016-10-31 03:00:03

标签: .net unit-testing task-parallel-library task

为什么这个测试失败?
据我所知,t1t2之间的唯一区别是t1Taskt2Task<int> }。但由于某种原因,t2最终处于Faulted状态,而不是Canceled状态。为什么行为会有所不同?

[Test]
public void Test_foo()
{
    var t1 = Task.Run(() =>
    {
        throw new OperationCanceledException();
    });
    try
    {
        t1.Wait();
    }
    catch (AggregateException e)
    {
        Assert.IsTrue(t1.IsCanceled);
    }

    var t2 = Task.Run(() =>
    {
        throw new OperationCanceledException();
        return 1;
    });
    try
    {
        t2.Wait();
    }
    catch (AggregateException e)
    {
        Assert.IsTrue(t2.IsCanceled); // fails, it's Faulted 
    }
}

1 个答案:

答案 0 :(得分:2)

您的任务之间的主要区别在于您正在使用的Task.Run方法的重载:

task1是使用Task.Run Method (Func<Task>)创建的,而task2是使用Task.Run<TResult> Method (Func<TResult>)创建的。这种重载确实创造了一点点差异:

  • task1 Result属性设为System.Threading.Tasks.VoidTaskResultCreationOptions设为None
  • 而不是task2 CreationOptions设置为DenyChildAttach,结果为default(int),即0

当您等待task2时,Result属性未设置为实际值,因为抛出了异常。根据{{​​3}}:

  

当任务实例观察到引发的MSDN时   用户代码,它将异常的令牌与其关联的令牌进行比较   (传递给创建Task的API的那个)。如果他们   是相同的,令牌的OperationCanceledException属性返回    true ,该任务将此解释为确认取消和   转换到Canceled状态。如果您不使用IsCancellationRequestedWait   等待任务的方法,然后任务只是将其状态设置为   Canceled

     

如果您正在等待转换为的任务   Canceled州,WaitAll   抛出异常(包含在System.Threading.Tasks.TaskCanceledException异常中)。注意   此异常表示成功取消而不是   错误的情况。因此,任务的AggregateException属性返回   的

     

如果令牌的Exception属性返回false或   如果异常的令牌与任务的令牌不匹配,则   IsCancellationRequested 被视为正常异常,导致   要转换到Faulted状态的任务。还要注意   存在其他异常也会导致Task转换为   Faulted州。Equals。您可以在中获取已完成任务的状态   OperationCanceledException财产。

所以,在这里我们可以找到这种行为的原因 - 由于令牌不匹配,异常被视为正常异常。这很奇怪,因为令牌是Status(我在Debug中检查过,哈希码相等,true方法和double等于运算符返回false),但比较仍然返回var t1TokenSource = new CancellationTokenSource(); var t1 = Task.Run(() => { Thread.Sleep(1000); if (t1TokenSource.Token.IsCancellationRequested) { t1TokenSource.Token.ThrowIfCancellationRequested(); } //throw new TaskCanceledException(); }, t1TokenSource.Token); try { t1TokenSource.Cancel(); t1.Wait(); } catch (AggregateException e) { Debug.Assert(t1.IsCanceled); } var t2TokenSource = new CancellationTokenSource(); var t2 = Task.Run(() => { Thread.Sleep(1000); if (t2TokenSource.Token.IsCancellationRequested) { t2TokenSource.Token.ThrowIfCancellationRequested(); } //throw new TaskCanceledException(); return 1; }, t2TokenSource.Token); try { t2TokenSource.Cancel(); t2.Wait(); } catch (AggregateException e) { Debug.Assert(t2.IsCanceled); } 。所以,你的案例的解决方案是明确使用definitely the same,就像这样(我添加了cancellation tokens以避免竞争条件):

TaskStatus.RanToCompletion

来自Thread.Sleep的另一句话:

  

您可以使用以下选项之一终止操作:

     
      
  • 只需从代表处返回即可。在许多情况下,这已足够;但是,以这种方式取消的任务实例   转换到TaskStatus.Canceled状态,而不是转换为   Canceled州。
  •   
  • 投掷MSDN并传递请求取消的令牌。 这样做的首选方法是   使用OperationCanceledException方法。一项任务   以这种方式取消过渡到task状态,其中   调用代码可以用来验证任务是否响应了它   取消请求。
  •   

正如您所看到的,preffered方式是可预测的,直接异常抛出没有。另请注意,如果使用情况DenyChildAttach也是//int arrayOverEightPounds[]; List<Integer> listOverEightPounds = new ArrayList<>(); //... //arrayOverEightPounds[k] = array[i]; listOverEightPounds.add(array[i]); //.... return listOverEightPounds.toArray(new Integer[0]); 创建的,并且没有ThrowIfCancellationRequested属性,那么构建器中存在一些差异,您已经遇到过。
希望这会有所帮助。