在数据流网络中使用BufferBlock <t>的好处</t>

时间:2012-10-08 11:52:16

标签: c# .net task-parallel-library tpl-dataflow

我想知道使用链接到一个或多个ActionBlock的BufferBlock是否有好处,除了限制(使用BoundedCapacity),而不是直接发布到ActionBlock(只要不需要限制)。

3 个答案:

答案 0 :(得分:19)

要添加到svick的答案,缓冲区块还有另一个好处。如果您有一个具有多个输出链接的块并希望在它们之间取得平衡,则必须将输出块转为非贪婪并添加缓冲区块来处理排队。我发现以下示例很有用:

引用现已死亡的链接:

这是我们计划做的事情:

  • 某些代码块会使用Post(T t)方法将数据发布到BufferBlock。
  • 此BufferBlock使用BufferBlock的LinkTo t)方法链接到3个ActionBlock实例。

注意,BufferBlock不会将输入数据的副本切换到它链接到的所有目标块。相反,它只对一个目标块执行此操作。我们期望当一个目标忙于处理请求时.It将被移交给另一个目标。现在让我们参考下面的代码:

static void Main(string[] args)
{
    BufferBlock<int> bb = new BufferBlock<int>();

    ActionBlock<int> a1 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(100);
        Console.WriteLine("Action A1 executing with value {0}", a);
    });

    ActionBlock<int> a2 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(50);
        Console.WriteLine("Action A2 executing with value {0}", a);
    });

    ActionBlock<int> a3 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(50);
        Console.WriteLine("Action A3 executing with value {0}", a);
    });

    bb.LinkTo(a1);
    bb.LinkTo(a2);
    bb.LinkTo(a3);

    Task t = new Task(() =>
        {
            int i = 0;
            while (i < 10)
            {
                Thread.Sleep(50);
                i++;
                bb.Post(i);
            }
        }
    );

    t.Start();
    Console.Read();
}

执行时会产生以下输出:

  • 操作A1以值1执行
  • 以值2执行的操作A1
  • 以值3执行的操作A1
  • 以值4执行的操作A1
  • 操作A1以值5执行
  • 动作A1以值6执行
  • 以值7执行的操作A1
  • 操作A1以值8执行
  • 以值9执行的操作A1
  • 以值10
  • 执行的操作A1

这表明只有一个目标实际上正在执行所有数据,即使它很忙(由于故意添加了Thread.Sleep(100))。为什么?

这是因为默认情况下所有目标块都是贪婪的,并且即使它们无法处理数据也会缓冲输入。为了改变这种行为,我们在DataFlowBlockOptions中将Greedy属性设置为false,同时初始化ActionBlock如下图所示。

static void Main(string[] args)
{
    BufferBlock<int> bb = new BufferBlock<int>();
    ActionBlock<int> a1 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(100);
            Console.WriteLine("Action A1 executing with value {0}", a);
        }
        , new DataflowBlockOptions(taskScheduler: TaskScheduler.Default,
            maxDegreeOfParallelism: 1, maxMessagesPerTask: 1,
            cancellationToken: CancellationToken.None,
            //Not Greedy
            greedy: false));

    ActionBlock<int> a2 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A2 executing with value {0}", a);
        }
        , new DataflowBlockOptions(taskScheduler: TaskScheduler.Default,
            maxDegreeOfParallelism: 1, maxMessagesPerTask: -1,
            cancellationToken: CancellationToken.None,
            greedy: false));
    ActionBlock<int> a3 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A3 executing with value {0}", a);
        }
        , new DataflowBlockOptions(taskScheduler: TaskScheduler.Default,
            maxDegreeOfParallelism: 1, maxMessagesPerTask: -1,
            cancellationToken: CancellationToken.None,
            greedy: false));

    bb.LinkTo(a1);
    bb.LinkTo(a2);
    bb.LinkTo(a3);

    Task t = new Task(() =>
    {
        int i = 0;
        while (i < 10)
        {
            Thread.Sleep(50);
            i++;
            bb.Post(i);
        }
    });

    t.Start();
    Console.Read();
}

该程序的输出是:

  • 操作A1以值1执行
  • 以值3执行的动作A2
  • 以值2执行的操作A1
  • 行动A3以值6执行
  • 行动A3以值7执行
  • 动作A3以值8执行
  • 以值5执行的动作A2
  • 行动A3以值9执行
  • 以值4执行的操作A1
  • 以值10执行的操作A2

这显然是按预期在三个ActionBlock中分布数据。

答案 1 :(得分:18)

如果您只想将项目从一个区块转发到其他区块,则不需要BufferBlock

但肯定会有一些有用的案例。例如,如果您有一个复杂的数据流网络,您可能希望从较小的子网络构建它,每个子网络都使用自己的方法创建。要做到这一点,你需要一些方法来表示一组块。在您提到的情况下,从方法返回单个BufferBlock(可能为ITargetBlock)将是一个简单的解决方案。

另一个BufferBlock有用的示例是,您希望将项目从多个源块发送到多个目标块。如果您使用BufferBlock作为中介,则不必将每个源块连接到每个目标块。

我确信还有很多其他例子可以使用BufferBlock。当然,如果您没有看到任何理由在您的情况下使用它,那么请不要。

答案 2 :(得分:4)

不,第二个例子不会因为多种原因而编译:只能为“分组”数据流块设置greedy = false - 不能为执行块设置;然后它必须通过GroupingDataflowBlockOptions设置 - 而不是DataflowBlockOptions;然后将其设置为属性值“{Greedy = false}”而不是构造函数参数。

如果要限制动作块的容量,可以通过设置DataflowBlockOptions的BoundedCapacity属性的值来实现(尽管如OP所述,他们已经知道此选项)。像这样:

ndk-build.cmd -B V=1 APP_ABI=armeabi
del /f/q c:\AndroidStudioProjects\Test\Test\src\main\libs\arm64-v8a\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\armeabi\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\armeabi-v7a\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\mips\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\mips64\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\x86\libJPEGProcessing.so c:\AndroidStudioProjects\Test\Test\src\main\libs\x86_64\libJPEGProcessing.so >NUL 2>NUL
[armeabi] SharedLibrary  : libJPEGProcessing.so
C:/Android/sdk/ndk-bundle/build//../toolchains/llvm/prebuilt/windows-x86_64/bin/clang++.exe -Wl,-soname,libJPEGProcessing.so -shared --sysroot=C:/Android/sdk/ndk-bundle/build//../platforms/android-9/arch-arm -lgcc  -gcc-toolchain C:/Android/sdk/ndk-bundle/build//../toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64 -no-canonical-prefixes -target armv5te-none-linux-androideabi  -Wl,--build-id -Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--warn-shared-textrel -Wl,--fatal-warnings    -lc -lm -o c:/AndroidStudioProjects/Test/Test/src/main/obj/local/armeabi/libJPEGProcessing.so
[armeabi] Install        : libJPEGProcessing.so => libs/armeabi/libJPEGProcessing.so
copy /b/y "c:\AndroidStudioProjects\Test\Test\src\main\obj\local\armeabi\libJPEGProcessing.so" "c:\AndroidStudioProjects\Test\Test\src\main\libs\armeabi\libJPEGProcessing.so" > NUL