C# 使用LinkTo谓词的TPL数据流块

C# 使用LinkTo谓词的TPL数据流块,c#,unit-testing,task-parallel-library,C#,Unit Testing,Task Parallel Library,我有一些块最终从TransformBlock转到基于LinkTo谓词的其他三个转换块之一。我正在使用DataflowLinkOptions传播完成。问题是,当满足谓词且该块已启动时,我的管道的其余部分将继续。管道似乎应该等待这个区块首先完成 代码如下所示: var linkOptions = new DataflowLinkOptions {PropagateCompletion = true}; mainBlock.LinkTo(block1, linkOptions, x => x.S

我有一些块最终从TransformBlock转到基于LinkTo谓词的其他三个转换块之一。我正在使用DataflowLinkOptions传播完成。问题是,当满足谓词且该块已启动时,我的管道的其余部分将继续。管道似乎应该等待这个区块首先完成

代码如下所示:

var linkOptions = new DataflowLinkOptions {PropagateCompletion = true};
mainBlock.LinkTo(block1, linkOptions, x => x.Status = Status.Complete);
mainBlock.LinkTo(block2, linkOptions, x => x.Status = Status.Cancelled);
mainBlock.LinkTo(block3, linkOptions, x => x.Status = Status.Delayed);
mainBlock.LinkTo(DataflowBlock.NullTarget<Thing>(), linkOptions);
var linkOptions=newdataflowlinkoptions{PropagateCompletion=true};
mainBlock.LinkTo(block1,linkOptions,x=>x.Status=Status.Complete);
mainBlock.LinkTo(block2,linkOptions,x=>x.Status=Status.Cancelled);
mainBlock.LinkTo(block3,linkOptions,x=>x.Status=Status.Delayed);
mainBlock.LinkTo(DataflowBlock.NullTarget(),linkOptions);
现在,正如我所说的,这并没有像我预期的那样起作用,所以我发现获得我想要的行为的唯一方法是取出linkOptions并将以下内容添加到main块的lambda中

mainBlock = new TransformBlock<Thing,Thing>(input =>
{
    DoMyStuff(input);

    if (input.Status = Status.Complete)
    {
        mainBlock.Completion.ContinueWith(t => block1.Complete());
    }
    if (input.Status = Status.Cancelled)
    {
        mainBlock.Completion.ContinueWith(t => block2.Complete());
    }
    if (input.Status = Status.Delayed)
    {
        mainBlock.Completion.ContinueWith(t => block3.Complete());
    }

    return input;
});
mainBlock=newtransformblock(输入=>
{
DoMyStuff(输入);
如果(input.Status=Status.Complete)
{
mainBlock.Completion.ContinueWith(t=>block1.Complete());
}
如果(input.Status=Status.Cancelled)
{
mainBlock.Completion.ContinueWith(t=>block2.Complete());
}
如果(input.Status=Status.Delayed)
{
mainBlock.Completion.ContinueWith(t=>block3.Complete());
}
返回输入;
});
所以问题是,这是唯一能让它发挥作用的方法吗

顺便说一句,这已经在我的单元测试中运行,其中有一个数据项通过它来尝试和调试管道行为。每个试块已通过多个单元测试进行单独测试。因此,在我的管道单元测试中,断言在块完成执行之前被命中,因此失败


如果我删除block2和block3链接并使用linkOptions调试测试,它工作正常。

确定。所以我首先要感谢科里。当我第一次读到他的评论时,我有点恼火,因为我觉得我的代码很好地说明了这个概念,可以很容易地转换成一个工作版本。但无论如何,我觉得有必要做一个完整的可测试版本,因为他的评论我可以发布

在我的测试中,令人惊讶的是,尽管它模仿了我的真实代码——我认为会通过的路径和我认为会通过的路径都失败了。这让我有点头晕。所以我开始对原始代码进行更多的排列。基本上,我创建了同步块和异步块,并创建了这两种管道。总共四个,2个同步和2个异步,其中一个用于传播链接选项,另一个用于在主块中完成任务,如图所示

在向异步任务添加一些任务延迟之后,我发现同步版本通过了测试,而异步版本失败了

因此,问题的最终解决方案不是上述任何一种。事实证明,链接选项传播(至少这是我的猜测)将传播不满足linkTo中谓词的块的完成。因此,当一个状态为“完成”的对象关闭时,它将进入Block1

哦,我应该指出,在完整的测试代码中,我让所有块1、2和3连接到同一个端块,这在原始代码中没有显示

不管怎么说,在谓词满足并且事情转到Block1之后,我相信block2和block3就完成了。这会导致结束块完成,我们正在单元测试中等待结束块完成,断言失败,因为Block1还没有完成它的工作

如果我的猜测是正确的,我感觉这是链接选项完成传播中的错误或设计错误。如果一个块从未使用过,为什么它应该是完整的

下面是我为解决这个问题所做的。我拿出链接选项,手动连接完成事件。像这样:

MainBlock.Completion.ContinueWith(t =>
{
Block1.Complete();
Block2.Complete();
Block3.Complete();
});

Task.WhenAll(Block1.Completion, Block2.Completion, Block3.Completion)
.ContinueWith(t =>
{
    EndBlock.Complete();
});
这工作得很好,当移到我的真实代码时也能工作。Task.WhenAll使我相信未使用的块被设置为完成,以及为什么自动传播是个问题

我希望这对某人有帮助。当我发布所有测试代码时,我会回来添加一个链接

编辑:
这里有一个指向测试代码要点的链接

您的问题不在于问题中正确运行的代码:当主块完成时,所有三个后续块也标记为完成

问题在于结束块:您在那里也使用了
PropagateCompletion
,这意味着当前面三个块中的任何一个完成时,结束块被标记为完成。您要做的是在所有三个块都完成时将其标记为完成,并且您的答案中的
任务.WhenAll().ContinueWith()
组合会这样做(尽管该片段的第一部分是不必要的,但它的作用与
任务完成的作用完全相同)

事实证明,链接选项传播(至少这是我的猜测)将传播不满足linkTo中谓词的块的完成

是的,它总是传播完成。完成没有任何与之关联的项,因此对其应用谓词没有任何意义。也许事实上,你总是只有一个单一的项目(这是不常见的),这让你更困惑

如果我的猜测是正确的,我感觉这是链接选项完成传播中的错误或设计错误。如果一个块从未使用过,为什么它应该是完整的

为什么不应该呢?对我来说,这非常有意义:即使这次没有状态为
的项目。延迟
,您仍然希望完成处理这些项目的块,以便任何后续代码都可以知道所有延迟项目都已处理。事实上,没有任何问题


不管怎样,如果你经常遇到这种情况,
public static void LinkTo<T>(
    this IReadOnlyCollection<ISourceBlock<T>> sources, ITargetBlock<T> target,
    bool propagateCompletion)
{
    foreach (var source in sources)
    {
        source.LinkTo(target);
    }

    if (propagateCompletion)
        Task.WhenAll(sources.Select(source => source.Completion))
            .ContinueWith(_ => target.Complete());
}
new[] { block1, block2, block3 }.LinkTo(endBlock, propagateCompletion: true);