C# 取消TPL数据流块的正确方法

C# 取消TPL数据流块的正确方法,c#,task-parallel-library,tpl-dataflow,cancellationtokensource,C#,Task Parallel Library,Tpl Dataflow,Cancellationtokensource,我正在使用TPL块执行用户可能取消的操作: 我提出了两个选项,首先我取消整个块,但不取消块内的操作,如下所示: _downloadCts = new CancellationTokenSource(); var processBlockV1 = new TransformBlock<int, List<int>>(construct => { List<int> properties = GetPropertiesMethod(construc

我正在使用
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 });
\u downloadCts=new CancellationTokenSource();
var processBlockV1=新的转换块(构造=>
{
列表属性=GetPropertiesMethod(构造);
var实体=属性
.天冬酰胺()
.选择(含数据的剂量计)
.ToList();
返回实体;
},新的ExecutionDataflowBlockOptions(){CancellationToken=\u 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;
});
var processBlockV2=新的转换块(构造=>
{
列表属性=GetPropertiesMethod(构造);
var实体=属性
.AsParallel().WithCancellation(_downloadCts.Token)
.选择(含数据的剂量计)
.ToList();
返回实体;
});

据我所知,第一个选项将取消整个阻塞,从而关闭整个管道。我的问题是,它是否也会取消内部操作并处置所有资源(如有)(开放式StreamReaders等),还是选择第二个选项更好,因为这样我自己就可以确保取消并清理所有内容,然后我可以使用一些方法(铁路规划)要将提升的
操作取消异常
沿管道向下浮动,并在我想要的地方处理它?

这两个选项并不等效

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

  • 第二个选项(取消内部操作)对整个块没有影响。数据流块允许从其处理函数抛出任何错误,只需忽略错误项并继续下一个。因此,在取消令牌后,所有已发布的消息仍将被处理,块将继续接受更多消息。它只是不会将任何内容传播到其链接的块,因为所有项都将抛出
    操作取消异常
    ,并被忽略。在特定示例中,将为所有
    construct
    消息调用
    GetPropertiesMethod
    方法,因此将对块的完成施加延迟。块的最终状态将是
    RanToCompletion


  • 重要的是要知道,数据流块认真对待
    完成的概念。在报告完成之前,他们将等待他们知道的一切停止运行。如果您确实希望他们提前完成并留下仍在运行的任务,您必须采取以下技巧。

    您应该将取消令牌传递给所有方法“取消”仅表示您请求结束任务。除非有人检查这个标志,否则它不会结束。这意味着
    DoSometheningWithData()
    也应该检查
    \u downloadCts
    ,可能是
    \u downloadCts.throwifccancellationrequested()
    。如果您处理的是一次性资源,请将它们放在
    using()
    块中,以确保它们被丢弃。TPL的真正功能是从几个不同的构建块构建管道。因此,它的设计方式是,每当
    ISourceBlock
    结束时(无论出于何种原因),它都可以通知所有链接的
    ITargetBlock
    s。为了这样工作,你必须在你的<代码>链接>代码> >调用:<代码>新的DATAFOLLKOOREST { ExpActudioPrime=真} /代码>我的建议是考虑取消制作人部分,而不是CuffelHi,特奥多尔,谢谢你的回答!基于此信息,我认为我的案例的最佳方案是在块定义本身中使用
    newexecutiondataflowblockoptions(){CancellationToken=\u downloadCts.Token}
    ,然后将相同的令牌传递给内部处理方法以停止它正在做的任何事情。我对它进行了测试,相同的令牌能够取消块,然后它在内部方法中再次“激发”。我的问题是-它能保证它在内部方法中总是第二次开火吗?嗨@niks!是,它将为当前正在处理的所有项目激发。当前处理的项的数量主要取决于传递到块的构造函数中的设置。默认情况下是1。我明白了。还有一件有趣的事,如果我使用Try library()和wrap异常,这样它们就可以沿着管道传递,如果
    CancelationToken
    TransformBlock
    上和内部都被激发,
    OperationCanceledException
    即使在内部处理方法中抛出,也不会向下浮动到最后一个块。不确定这是否是一个问题,但我认为这是实施此类解决方案时应该注意的事项。@niks是的,我也注意到了这一点,而且似乎不太符合逻辑。实际上,虽然相同的取消令牌通常会传递给管道的所有块,因此,在大多数情况下,这种意外行为都不会出现。嗯……我现在在想,如果我将相同的
    cancelationToken
    传递给所有块,并且我使用前面提到的
    Try
    库来包装每个异常并将其作为一个简单的值在管道中浮动,设置
    新的DataflowLinkOptions{PropagateCompletion=true}
    将块链接在一起时有任何