链接的数据流块如何取消目标块

时间:2018-07-25 06:04:44

标签: c# tpl-dataflow

在TPL数据流中,当一个块通过传播链接到另一个块时,它将转发异常以及取消。我可以想象通过使用dataFlowBlock.Fault(exception)来简单地转发一个异常,但是我很好奇如何取消该转发,因为没有dataFlowBlock.Cancel()这样的东西。是通过传递Fault()作为参数,通过相同的TaskCancelledException方法完成的吗?

更新:

为清楚起见,请考虑以下示例,其中仅通过选项使用CancelationToken创建了block1,而没有创建block2。 Block1通过传播链接到block2:

block1 { CancellationToken = ct } -> block2 { }

当ct收到取消请求时,block1完成转换为取消。我的问题是此时Block2会发生什么? block1是否主动取消block2,如果是,则使用block.Fault(TaskCanceledException)来取消吗?还是使用某些内部轨迹来神奇地取消block2,即使它是在没有取消令牌的情况下创建的?

3 个答案:

答案 0 :(得分:1)

取消的形式为ExecutionDataflowBlockOptions上的选项CancellationToken,传递的令牌将成功完成块。这意味着块将OperationCanceledException与流水线中抛出的其他异常区别对待,并导致块一旦完成处理就简单地完成。取消旨在通过共享单个CTS并将关联的令牌用于块选项来起作用。然后,当取消CTS时,所有块都会收到取消信号。

还有更多here from MS

在评论中指向您的实际问题,这可能会进一步说明问题。

  

显式取消数据流块时,AggregateException对象的InnerExceptions属性中包含OperationCanceledException

也:

  

TPL提供了一种机制,使任务能够以协作方式协调取消。要使数据流块能够参与此取消机制,请设置CancellationToken属性。当将此CancellationToken对象设置为cancelled状态时,监视此令牌的所有数据流块都将完成其当前项目的执行,但不会开始处理后续项目。这些数据流块还清除所有缓冲的消息,释放与任何源块和目标块的连接,并转换为取消状态。通过转换为取消状态,Completion属性会将Status属性设置为Canceled,除非在处理过程中发生异常。在这种情况下,“状态”设置为“故障”。

Source

这是引号很多的文字,但是提供了源链接,并且说明写得很好。

因此,这些块不会主动传播抵消。但是,一旦取消的块完成,并且传播到true,就将完成任务连同取消或故障状态一起流向

答案 1 :(得分:1)

好的,我认为我们与更新的帖子在同一页面上。简而言之,如果传播不正确,则取消不会流到链接的块上。

[TestFixture]
public class DataFlowTests
{

    [Test]
    public async Task DataflowTest()
    {
        var cts = new CancellationTokenSource();
        var buffer = new BufferBlock<int>(new DataflowBlockOptions() { BoundedCapacity = 200, CancellationToken = cts.Token });
        var action = new ActionBlock<int>(x => Task.Delay(100), new ExecutionDataflowBlockOptions() { BoundedCapacity = 5 });
        buffer.LinkTo(action, new DataflowLinkOptions() { PropagateCompletion = false});

        foreach (var data in Enumerable.Range(0, 20))
        {
            if (data > 10) break;
            await buffer.SendAsync(data);
        }
        cts.Cancel();
        //action.Complete();
        await action.Completion;
        Console.WriteLine(buffer.Completion.Status);
        Console.WriteLine(action.Completion.Status);
    }
}

该示例将永远挂起,等待action完成。现在,在Complete()上调用ActionBlock<>显式会产生以下结果:

Cancelled - buffer
RanToCompletion - action

最终传播完成会产生相同的结果,而无需在下游块上手动调用完成:

[TestFixture]
public class DataFlowTests
{

    [Test]
    public async Task DataflowTest()
    {
        var cts = new CancellationTokenSource();
        var buffer = new BufferBlock<int>(new DataflowBlockOptions() { BoundedCapacity = 200, CancellationToken = cts.Token });
        var action = new ActionBlock<int>(x => Task.Delay(100), new ExecutionDataflowBlockOptions() { BoundedCapacity = 5 });
        buffer.LinkTo(action, new DataflowLinkOptions() { PropagateCompletion = true});

        foreach (var data in Enumerable.Range(0, 20))
        {
            if (data > 10) break;
            await buffer.SendAsync(data);
        }
        cts.Cancel();
        await action.Completion;
        Console.WriteLine(buffer.Completion.Status);
        Console.WriteLine(action.Completion.Status);
    }
}

收益状态:

Canceled - buffer
RanToCompletion - action

请注意,取消不会不会导致整个管道被取消。其他块只需完成。现在,如果除取消的通知之外的其他异常通过,则管道将通过...Fault(..)出现故障。否则,它将突出标准完成传播。

答案 2 :(得分:1)

可以在herehere中找到在块将其完成传播到链接的块时运行的源代码。以下是此代码的简化版本:

internal static void PropagateCompletion(Task sourceCompletionTask, IDataflowBlock target)
{
    AggregateException exception =
        sourceCompletionTask.IsFaulted ? sourceCompletionTask.Exception : null;

    if (exception != null) target.Fault(exception); else target.Complete();
}

没有传播取消的规定。如果块在故障状态下完成,则异常会传播到链接的块。在任何其他情况下,链接的块都只是标记为完成。

AFAIK块转换到取消状态的唯一且唯一的情况是取消创建时提供的CancellationToken。例如,如果您尝试从其lambda函数内部throw new OperationCanceledException()开始,则什么也不会发生,而已处理的消息将被忽略。