在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,即使它是在没有取消令牌的情况下创建的?
答案 0 :(得分:1)
取消的形式为ExecutionDataflowBlockOptions
上的选项CancellationToken
,传递的令牌将成功完成块。这意味着块将OperationCanceledException
与流水线中抛出的其他异常区别对待,并导致块一旦完成处理就简单地完成。取消旨在通过共享单个CTS并将关联的令牌用于块选项来起作用。然后,当取消CTS时,所有块都会收到取消信号。
还有更多here from MS。
在评论中指向您的实际问题,这可能会进一步说明问题。
显式取消数据流块时,AggregateException对象的InnerExceptions属性中包含OperationCanceledException
也:
TPL提供了一种机制,使任务能够以协作方式协调取消。要使数据流块能够参与此取消机制,请设置CancellationToken属性。当将此CancellationToken对象设置为cancelled状态时,监视此令牌的所有数据流块都将完成其当前项目的执行,但不会开始处理后续项目。这些数据流块还清除所有缓冲的消息,释放与任何源块和目标块的连接,并转换为取消状态。通过转换为取消状态,Completion属性会将Status属性设置为Canceled,除非在处理过程中发生异常。在这种情况下,“状态”设置为“故障”。
这是引号很多的文字,但是提供了源链接,并且说明写得很好。
因此,这些块不会主动传播抵消。但是,一旦取消的块完成,并且传播到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)
可以在here或here中找到在块将其完成传播到链接的块时运行的源代码。以下是此代码的简化版本:
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()
开始,则什么也不会发生,而已处理的消息将被忽略。