2012-10-08 12 views

Respuesta

15

Si todo lo que desea hacer es reenviar elementos de un bloque a varios otros, no necesita BufferBlock.

Pero ciertamente hay casos en que es útil. Por ejemplo, si tiene una red de flujo de datos compleja, es posible que desee construirla a partir de subredes más pequeñas, cada una creada en su propio método. Y para hacer esto, necesitas alguna forma de representar un grupo de bloques. En el caso que usted mencionó, devolver ese único BufferBlock (probablemente como ITargetBlock) del método sería una solución fácil.

Otro ejemplo en el que BufferBlock sería útil es si desea enviar elementos de varios bloques de origen a varios bloques de destino. Si utilizó BufferBlock como intermediario, no tiene que conectar cada bloque de origen a cada bloque de destino.

Estoy seguro de que hay muchos otros ejemplos donde puede usar BufferBlock. Por supuesto, si no ve ningún motivo para usarlo en su caso, no lo haga.

+0

me siento que el uso de BufferBlocks es la forma más "limpia" de comunicación entre bloques de flujo de datos, sino que es la cabeza (si lo hay) de la utilización de BufferBlocks vale la pena? – Dimitri

+1

Eso es para que usted decida. Si crees que hace que tu código sea más limpio, hazlo. Tiene algunos gastos generales, pero creo que no debería ser notable, a menos que realmente se preocupe por el rendimiento. – svick

19

Para agregar a la respuesta de svick, hay otra ventaja de los bloques de almacenamiento. Si tiene un bloque con múltiples enlaces de salida y desea equilibrarlos, debe convertir los bloques de salida en no codiciosos y agregar un bloque de almacenamiento para manejar la cola. He encontrado el siguiente ejemplo útil:

Citado de un enlace que está ahora muerto:

Esto es lo que estamos planeando hacer:

  • Algunos bloque de código va a publicar datos a la BufferBlock utilizando su Método de publicación (T t).
  • Este BufferBlock está vinculado a 3 instancias ActionBlock utilizando el método LinkTo t) de BufferBlock.

Nota, que no BufferBlock copias de transferencia de los datos de entrada a todos los bloques de destino se vincula to.Instead lo hace a un bloque de destino only.Here estamos esperando que cuando un objetivo está ocupado procesando la solicitud. Será entregado al otro objetivo.Ahora vamos a hacer referencia al código de abajo:

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(); 
    } 

Cuando ejecuta produce el siguiente resultado:

  • Acción A1 ejecutar con valor 1
  • Acción A1 ejecutar con un valor de 2
  • Acción A1 ejecución con valor 3
  • Acción A1 ejecutar con valor 4
  • Acción A1 ejecutar con valor 5
  • Acción A1 ejecución con valor 6
  • Acción A1 ejecución con valor 7
  • Acción A1 ejecución con valor 8
  • Acción A1 ejecución con valor 9
  • Acción A1 ejecución con valor 10

Esto muestra que solo un objetivo está ejecutando todos los datos, incluso cuando está ocupado (debido a Thread.Sleep (100) agregado deliberadamente). ¿Por qué?

Esto se debe a que todos los bloques de destino son de naturaleza por defecto codiciosa y almacena la entrada de entrada incluso cuando no pueden procesar los datos. Para cambiar este comportamiento, hemos establecido la propiedad Greedy en falso en DataFlowBlockOptions mientras se inicializa ActionBlock como se muestra a continuación.

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(); 
    } 

La salida de este programa es:

  • Acción A1 ejecución con valor 1
  • Acción A2 ejecución con valor 3
  • Acción A1 ejecución con valor 2
  • Acción A3 ejecución con valor 6
  • Acción A3 que se ejecuta con el valor 7
  • Acción A3 ejecución con valor 8
  • Acción A2 ejecución con valor 5
  • Acción A3 ejecución con valor 9
  • Acción A1 ejecución con valor 4
  • Acción A2 ejecución con valor 10

Este claramente una distribución de los datos en tres ActionBlock (s) como se esperaba.

+0

No se pudo obtener el segundo ejemplo para compilar. – Nathan

4

No, el segundo ejemplo no se compilará por una serie de razones: solo es posible establecer greedy = false para un bloque de flujo de datos "de agrupación", no para un bloque de ejecución; y luego tiene que establecerse a través de GroupingDataflowBlockOptions - no DataflowBlockOptions; y luego se establece como un valor de propiedad "{Greedy = false}" no como un parámetro de constructor.

Si desea reducir la capacidad de un bloque de acción, hágalo estableciendo el valor de la propiedad BoundedCapacity de DataflowBlockOptions (aunque, como indica el OP, ya conocen esta opción).De esta manera:

var a1 = new ActionBlock<int>(
      i => doSomeWork(i), 
      new ExecutionDataflowBlockOptions {BoundedCapacity = 1} 
     ); 
Cuestiones relacionadas