【问题标题】:Correct way to cancel TPL data flow block取消TPL数据流块的正确方法
【发布时间】:2020-05-29 07:19:00
【问题描述】:

我正在使用TPL 块来执行可能被用户取消的操作: 我提出了两个选项,首先我取消整个块但不取消块内的操作,如下所示:

_downloadCts = new CancellationTokenSource();

var processBlockV1 = new TransformBlock<int, List<int>>(construct =>
{
    List<int> properties = GetPropertiesMethod(construct );
    var entities = properties
        .AsParallel()
        .Select(DoSometheningWithData)
        .ToList();
    return entities;
}, new ExecutionDataflowBlockOptions() { CancellationToken = _downloadCts.Token });

第二个我取消内部操作,但不是块本身:

var processBlockV2 = new TransformBlock<int, List<int>>(construct =>
{
    List<int> properties = GetPropertiesMethod(construct);
    var entities = properties
        .AsParallel().WithCancellation(_downloadCts.Token)
        .Select(DoSometheningWithData)
        .ToList();
    return entities;
});

据我了解,第一个选项将取消整个块,从而关闭整个管道。我的问题是它是否也会取消内部操作并处置所有资源(如果有的话(打开 StreamReaders 等)或者最好选择第二个选项,然后我自己可以确保所有内容都被取消和清理,然后我可以使用一些方法(铁路编程)将OperationCanceledException 浮出管道并在我想要的地方处理它?

【问题讨论】:

  • 您应该将取消令牌传递给所有方法
  • "Cancel" 只是表示你请求一个愿望 让事情结束。除非有东西检查这个标志,否则它不会结束。这意味着DoSometheningWithData() 也应该与_downloadCts 一起检查_downloadCts.ThrowIfCancellationRequested ()。如果您正在处理一次性资源,请将它们放在 using() 块中以确保它们被丢弃
  • TPL 的真正威力是从几个不同的构建块构建管道。因此,它的设计方式是,每当ISourceBlock 结束(无论出于何种原因),它都可以通知所有链接的ITargetBlocks。为了像这样工作,您必须在 Link 通话期间指定以下内容:new DataflowLinkOptions { PropagateCompletion = true } 我的建议是考虑取消 producer 部分,而不是 consumer

标签: c# task-parallel-library tpl-dataflow cancellationtokensource


【解决方案1】:

这两个选项不等价。

  1. 第一个选项 (CancellationToken = _downloadCts.Token) 将使processBlockV1 块丢弃当前在其缓冲区中的任何消息(其InputCount 属性将变为0),并停止接受新消息(调用其Post 方法将始终返回false)。它不会停止处理当前正在进行的消息。这些将被完全处理,但不会传播到任何链接的块。处理完这些消息后,块将以取消状态完成(其Completion.Status 属性将变为Canceled)。

  2. 第二个选项(取消内部操作)对整个块没有影响。 Dataflow 块容忍从其处理函数中抛出的任何OperationCanceledException,并忽略有故障的项目并继续下一个项目。所以取消令牌后,所有发布的消息仍然会被处理,并且块将继续接受更多。它只是不会将任何东西传播到它的链接块,因为所有项目都会抛出 OperationCanceledException 并被忽略。在特定示例中,将为所有construct 消息调用GetPropertiesMethod 方法,因此将对块的完成施加延迟。区块的最终状态将是RanToCompletion

重要的是要知道 Dataflow 块正在认真对待Completion 的概念。在报告完成之前,他们将等待他们知道的所有内容停止运行。如果您确实希望它们过早完成并留下仍在运行的任务,则必须使用wrapping your tasks with cancelable wrappers 之类的技巧。

【讨论】:

  • 嗨,西奥多,谢谢你的回答!基于这些信息,我认为我的情况的最佳方案是在块定义本身中使用new ExecutionDataflowBlockOptions() { CancellationToken = _downloadCts.Token },然后将相同的令牌传递给内部处理方法以停止它正在做的任何事情。我对其进行了测试,相同的令牌能够取消块,然后在内部方法中再次“触发”。我的问题是 - 是否保证它总是会在内部方法中第二次触发?
  • 嗨@niks!是的,它将针对当前正在处理的所有项目触发。当前处理的项目数主要取决于传递给块构造函数的设置MaxDegreeOfParallelism。默认是一个。
  • 我明白了。一件更有趣的事情是,如果我使用 Try library(github.com/StephenCleary/Try) 并包装异常以便它们可以通过管道传递,如果 CancelationTokenTransformBlock 上和内部都被触发 OperationCanceledException 不会向下浮动即使在内部处理方法中抛出了最终块。不确定这是否是一个问题,但我认为在实施此类解决方案时应该注意这一点。
  • @niks 是的,我也注意到了这一点,TBH 似乎不太合乎逻辑。实际上,虽然相同的取消令牌通常会传递给管道的所有块,但在大多数情况下,这种意外行为没有机会出现。
  • Hmm..我现在正在考虑是否将相同的 cancelationToken 传递给所有块,并且我正在使用前面提到的 Try 库来包装每个异常并将其作为简单值在管道中浮动,将块链接在一起时设置new DataflowLinkOptions { PropagateCompletion = true } 是否有任何实际功能?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多