最优的TPL数据流设计?

时间:2012-06-28 09:55:24

标签: c# architecture asynchronous concurrency tpl-dataflow

我想询问输入如何使用TPL Dataflow最佳地设计最佳架构。我还没有编写代码所以没有可以发布的示例代码。我不是在寻找代码(除非是自愿),但是对设计的帮助将非常感激:

要求如下:

我有3个核心数据块,它们以特定的方式相互依赖。 Datablock1是一个生成Foo1类型对象的生产者。 Datablock2应该订阅Foo1对象(来自Datablock1),并且可能(不是在每个Foo1上,受特定函数约束)产生Foo2对象,它存储在输出队列中以供其他数据块使用。 Datablock3还使用Foo1对象(来自Datablock1)并可能生成Datablock2使用的Foo3对象并转换为Foo2对象。

总之,这里是数据块以及它们每个产生和消费的内容:

  • Datablock1:生成(Foo1),消耗(无)
  • Datablock2:产生(Foo2),消耗(Foo1,Foo3)
  • Datablock3:生成(Foo3),消耗(Foo1)

另一个要求是在Datablock2和Datablock3中几乎同时处理相同的Foo1。如果Foo1对象首先被Datablock2使用,然后一旦Datablock2完成其工作,就会将相同的Foo1对象发布到Datablock3以使其完成工作。来自Datablock2的Foo2对象可以来自Foo1对象或Foo3对象上的操作。

我希望这是有道理的,如果目前还不清楚,我很乐意解释。

我的第一个想法是为3个数据块中的每一个创建TPL数据流块,并使它们处理不同对象类型的传入流。另一个想法是分割数据块并使每个数据块仅处理一个对象类型的流。您有什么建议,或者是否有更好的解决方案可行?

Svick已经帮助了Datablock1并且它已经运行了,我只是坚持如何将我当前的环境(如上所述)转换为TPL Dataflow。

非常感谢任何想法或指示。

1 个答案:

答案 0 :(得分:4)

让我们将这个问题分成三个并单独解决。

第一个是如何有条件地生产物品。我认为最好的选择是使用TransformManyBlock并让你的函数返回一个或零项的集合。

另一个选项是link the two blocks conditionally,以便忽略null并在您不想生成任何内容时返回null。 但是如果你这样做,你还必须将源链接到NullTarget,以便null不会保留在其输出缓冲区中。

第二个问题是如何将Foo1s发送到块#2和块#3。我可以在这里看到两种方式:

  1. 使用链接到两个目标块(#2和#3)的BroadcastBlock。请注意这一点,因为BroadcastBlock没有输出队列,所以如果目标块推迟了一个项目,则意味着它不会处理它。因此,在这种情况下,不应设置块#2和#3的BoundedCapacity。如果你不这样做,它们将永远不会推迟,所有消息都将由两个块处理。
  2. 按块#2处理Foo1后,手动Post()(或更好,SendAsync())阻止#3。
  3. 我不确定“几乎在同一时间”究竟是什么意思,但总的来说,TPL Dataflow不会对独立块的处理顺序做出任何保证。您可以使用a custom TaskScheduler更改不同块的优先级,但我不确定这在此处是否有用。

    最后也是最复杂的问题是如何在一个块中处理不同类型的项目。有几种方法可以做到这一点,虽然我不确定哪种方式最适合你:

    1. 不要在单个块中处理它们。有一个TransformBlock<Foo1, Foo2>和一个TransformBlock<Foo3, Foo2>。然后,您可以将它们链接到一个BufferBlock<Foo2>
    2. 根据您的建议,使用BatchedJoinBlock<Foo1, Foo3>batchSize为1.这意味着生成的Tuple<IList<Foo1>, IList<Foo3>>将始终包含一个Foo1或一个Foo3
    3. 通过将BatchedJoinBlock链接到产生更合适类型的TransformBlock来增强以前的解决方案。这可以是Tuple<Foo1, Foo3>(其中一个项目将始终为null),或者类似于F#Choice<Foo1, Foo3>,这可以确保只设置其中一个。
    4. 从头开始创建一个新的块类型,它完全符合您的要求。它应该是ISourceBlock<Foo2>并且还有两个属性:类型为Target1的{​​{1}}和类型为ITarget<Foo1>的{​​{1}},类似于内置的连接块。
    5. 使用选项#1和#3,您还可以将块封装到一个自定义块中,该块看起来像是来自外部的#4块,因此它更容易重用。