TPL Dataflows – część V (BatchBlock)

Dzisiaj pierwszy post o tzw. grouping block czyli blokach grupujących. Ich zasada jest prosta – grupują dane z różnych źródeł w sposób zależny już od konkretnego bloku. W tej części zajmiemy się BatchBlock, który przychodzące dane buforuje, a następnie przesuwa je na wyjście w zdefiniowanych porcjach.

BatchBlock działa w dwóch trybach: greedy i non-greedy. W przypadku implementacji zachłannej, wszystko co pojawia się na wejściu jest akceptowane i przekazywane na wyjście gdy uzbiera się określona liczba elementów. W przypadku trybu niezachłannego, wyłącznie dane, które mają określoną liczbę elementów będą akceptowane. Innymi słowy, jeśli zdefiniujemy batch jako 10 elementów, wtedy w trybie niezachłannym wyłącznie tablice o długości 10 lub więcej będą akceptowane, a reszta będzie uznawana jako postponed. Z kolei w zachłannym trybie, wszystko będzie akceptowane, buforowane i przekazane na wyjście gdy bufor odpowiednio zapełni się.

Domyślnie, BatchBlock działa w sposób zachłanny. Przejdźmy do przykładu:

class Program
{
   private static void Main(string[] args)
   {
       var batchBlock = new BatchBlock<int>(5);
       batchBlock.LinkTo(CreateActionBlock("Output A"));

       for (int i = 0; i < 7; i++)
           batchBlock.Post(i);

       Console.ReadLine();
   }
   private static ActionBlock<int[]> CreateActionBlock(string label)
   {
       return new ActionBlock<int[]>(delegate(int[] batch)
           {
               Console.WriteLine(label);

               foreach (var item in batch)
               {
                   Console.WriteLine(item);
               }
           });
   }
}

W konstruktorze definiujemy rozmiar batch’a (porcji) na 5 elementów. Następnie podłączamy do niego zwykłą akcję wyświetlającą wyjście. Pamiętajmy, że wyjście BatchBlock to tablica elementów czyli zbuforowane dane. Jeśli ustawiamy batch size na 5 wtedy, wyjście zawsze będzie stanowić tablica 5-elementowa. W powyższym przykładzie, mimo, że dodaliśmy 7 elementów to i tak 5 tylko zostanie wyświetlonych na ekranie. Zmieńmy teraz tryb na niezachłanny, aby zobaczyć w praktyce różnicę w działaniu:

class Program
{
   private static void Main(string[] args)
   {
       var batchBlock = new BatchBlock<int>(5, new GroupingDataflowBlockOptions {Greedy = false});
       batchBlock.LinkTo(CreateActionBlock("Output A"));            

       for (int i = 0; i < 5; i++)
           batchBlock.Post(i);

       Console.ReadLine();
   }

   private static ActionBlock<int[]> CreateActionBlock(string label)
   {
       return new ActionBlock<int[]>(delegate(int[] batch)
           {                    
               Console.WriteLine(label);

               foreach (var item in batch)
               {
                   Console.WriteLine(item);
               }
           });
   }
}

Na ekranie nic nie zobaczymy. Dlaczego? Jak wspomniałem, niezachłanna implementacja nie zaakceptuje wiadomości jeśli bufor nie zapełni się. W tym problem, że używając Post, nigdy nie uda nam się tego osiągnąć, ponieważ Post traktuje opóźnienie w przetwarzaniu danych jako ich odrzucenie. W dokumentacji znajdziemy następującą informację:

“For target blocks that support postponing offered messages, or for blocks that may do more processing in their Post implementation, consider using SendAsync, which will return immediately and will enable the target to postpone the posted message and later consume it after SendAsync returns.”

Zamieniając Post na SendAsync ujrzymy output na ekranie.  Wiadomość nie zostanie ani odrzucona ani zaakceptowana – po prostu w późniejszym czasie będzie mogła zostać przetworzona. Jeśli jest to jeszcze niejasne nie ma co się martwić – w następnych postach pokażę bardziej praktyczny przykład gdzie ustawienie trybu non-greedy jest przydatne. Po dzisiejszym wpisie należy wiedzieć, że tryb niezachłanny ustawia status jako postponed – czyli opóźnienie w przetwarzaniu, z kolei tryb zachłanny akceptuje natychmiast wszystkie dane.

Leave a Reply

Your email address will not be published.