Преимущества использования BufferBlock‹T› в сетях передачи данных

Мне было интересно, есть ли преимущества, связанные с использованием BufferBlock, связанного с одним или несколькими ActionBlock, кроме регулирования (с использованием BoundedCapacity), вместо того, чтобы просто публиковать непосредственно в ActionBlock(s) (пока регулирование не требуется).


person Dimitri    schedule 08.10.2012    source источник


Ответы (3)


Если все, что вы хотите сделать, это переслать элементы из одного блока в несколько других, вам не нужен BufferBlock.

Но есть, конечно, случаи, когда это полезно. Например, если у вас есть сложная сеть потоков данных, вы можете построить ее из более мелких подсетей, каждая из которых создается собственным методом. А для этого вам нужно каким-то образом представить группу блоков. В случае, который вы упомянули, возвращение этого единственного BufferBlock (возможно, как ITargetBlock) из метода было бы простым решением.

Другой пример, когда BufferBlock будет полезен, — это если вы хотите отправить элементы из нескольких исходных блоков в несколько целевых блоков. Если вы использовали BufferBlock в качестве посредника, вам не нужно подключать каждый исходный блок к каждому целевому блоку.

Я уверен, что есть много других примеров, где вы могли бы использовать BufferBlock. Конечно, если вы не видите смысла использовать его в вашем случае, то и не надо.

person svick    schedule 08.10.2012
comment
Я действительно считаю, что использование BufferBlocks является более чистым способом связи между блоками потока данных, но стоит ли накладные расходы (если таковые имеются) на использование BufferBlocks? - person Dimitri; 08.10.2012
comment
Это вам решать. Если вы чувствуете, что это делает ваш код чище, сделайте это. У него есть некоторые накладные расходы, но я думаю, что это не должно быть заметно, если вы действительно не заботитесь о производительности. - person svick; 08.10.2012

Чтобы добавить к ответу Свика, есть еще одно преимущество буферных блоков. Если у вас есть блок с несколькими выходными ссылками и вы хотите балансировать между ними, вы должны сделать выходные блоки ограниченной емкостью 1 и добавить буферный блок для обработки очереди.

Вот что мы планируем сделать:

  • Некоторый блок кода будет отправлять данные в BufferBlock, используя его метод Post(T t).
  • Этот BufferBlock связан с 3 экземплярами ActionBlock с помощью метода LinkTo t) BufferBlock.

Обратите внимание, что BufferBlock не передает копии входных данных всем целевым блокам, с которыми он связан. Вместо этого он делает это только с одним целевым блоком. Здесь мы ожидаем, что, когда одна цель занята обработкой запроса, он будет передан другой цели. Теперь давайте обратимся к коду ниже:

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
  • Действие A1 выполняется со значением 2
  • Действие A1 выполняется со значением 3
  • Действие A1 выполняется со значением 4
  • Действие A1 выполняется со значением 5
  • Действие A1 выполняется со значением 6
  • Действие A1 выполняется со значением 7
  • Действие A1 выполняется со значением 8
  • Действие A1 выполняется со значением 9
  • Действие A1 выполняется со значением 10

Это показывает, что только одна цель фактически выполняет все данные, даже когда она занята (из-за целенаправленного добавления Thread.Sleep(100)). Почему?

Это связано с тем, что все целевые блоки по умолчанию являются жадными по своей природе и буферизуют ввод, даже если они не могут обрабатывать данные. Чтобы изменить это поведение, мы установили для параметра Bounded Capacity значение 1 в DataFlowBlockOptions при инициализации 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 ExecutionDataflowBlockOptions {BoundedCapacity = 1});
    ActionBlock<int> a2 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A2 executing with value {0}", a);
        }
        , new ExecutionDataflowBlockOptions {BoundedCapacity = 1});
    ActionBlock<int> a3 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A3 executing with value {0}", a);
        }
        , new ExecutionDataflowBlockOptions {BoundedCapacity = 1});

    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
  • Действие A2 выполняется со значением 3
  • Действие A1 выполняется со значением 2
  • Действие A3 выполняется со значением 6
  • Действие A3 выполняется со значением 7
  • Действие A3 выполняется со значением 8
  • Действие A2 выполняется со значением 5
  • Действие A3 выполняется со значением 9
  • Действие A1 выполняется со значением 4
  • Действие A2 выполняется со значением 10

Это явное распределение данных по трем ActionBlock, как и ожидалось.

person VoteCoffee    schedule 03.12.2013
comment
Не удалось скомпилировать второй пример. - person Nathan; 27.04.2016
comment
Привет! Жадного поведения можно было бы достичь, просто установив для свойства BoundedCapacity property значение 1 в каждом из ActionBlock вместо установки всех этих конфигураций. настройки. - person José Roberto Araújo; 07.01.2020
comment
Я обновил этот ответ, чтобы удалить жадное свойство, которое предназначено только для группировки блоков потока данных. В идеале я хотел бы создать отдельный пример для случая группировки и использования greedy = false, так как это также хороший вариант использования для банных блоков. - person VoteCoffee; 07.01.2020

Нет, второй пример не скомпилируется по ряду причин: установить greedy=false можно только для «группирующего» блока потока данных, но не для исполнительного блока; и тогда его нужно установить через GroupingDataflowBlockOptions, а не через DataflowBlockOptions; а затем он устанавливается как значение свойства "{Жадный = false}", а не параметр конструктора.

Если вы хотите ограничить емкость блока действий, сделайте это, установив значение свойства BoundedCapacity DataflowBlockOptions (хотя, как заявил OP, они уже знают об этой опции). Как это:

var a1 = new ActionBlock<int>(
            i => doSomeWork(i), 
            new ExecutionDataflowBlockOptions {BoundedCapacity = 1}
        );
person Steve Blomeley    schedule 25.02.2017