Zastanawiam się, czy istnieją korzyści związane z używaniem BufferBlock połączonego z jednym lub wieloma blokami ActionBlock, innymi niż dławienie (za pomocą BoundedCapacity), zamiast zwykłego wysyłania bezpośrednio do ActionBlock (s) o ile dławienie nie jest wymagane).Korzyści z używania BufferBlock <T> w sieciach przepływów danych
Odpowiedz
Jeśli wszystko, co chcesz zrobić, to przesłać elementy z jednego bloku do kilku innych, nie potrzebujesz BufferBlock
.
Ale są oczywiście przypadki, w których jest to przydatne. Na przykład, jeśli masz złożoną sieć przepływu danych, możesz zbudować ją z mniejszych podsieci, z których każda jest tworzona we własnej metodzie. Aby to zrobić, potrzebujesz sposobu reprezentowania grupy bloków. W przypadku, o którym wspomniałeś, zwrócenie tego pojedynczego BufferBlock
(prawdopodobnie jako ITargetBlock
) z metody byłoby łatwym rozwiązaniem.
Innym przykładem, w którym przydatne byłoby BufferBlock
, jest to, że chcesz wysłać elementy z kilku bloków źródłowych do kilku bloków docelowych. Jeśli korzystałeś z BufferBlock
jako pośrednika, nie musisz łączyć każdego bloku źródłowego z każdym blokiem docelowym.
Jestem pewien, że istnieje wiele innych przykładów, w których można użyć BufferBlock
. Oczywiście, jeśli nie widzisz żadnego powodu, aby użyć go w twoim przypadku, nie rób tego.
Aby dodać do odpowiedzi svick, istnieje kolejna korzyść z bloków bufora. Jeśli masz blok z wieloma wyjściowymi łączami i chcesz je wyrównać, musisz przekierować bloki wyjściowe do nieagresywnych i dodać blok bufora do obsługi kolejkowania. Znalazłem następujący przykład przydatne:
Cytat z linkiem, który jest teraz martwy:
To, co planujemy zrobić:
- Niektóre blok kodu będzie dodawać dane do BufferBlock stosując to Metoda Post (T t).
- Ta BufferBlock jest połączona z instancją 3 ActionBlock przy użyciu metody LinkTo t) BufferBlock.
Zauważ, że BufferBlock nie kopiuje przekazania danych wejściowych do wszystkich bloków docelowych jest powiązany to.Instead robi to do jednego bloku docelowym only.Here oczekujemy, że gdy jeden docelowy jest zajęty przeróbki request.It zostanie przekazany do drugiego celu.Teraz patrz poniższy kod:
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();
}
Po wykonaniu produkuje następujące dane wyjściowe:
- Action A1 wykonującemu wartości 1
- Action A1 wykonujący o wartości 2
- Action A1 wykonującego o wartości 3
- Wykonanie czynności A1 wartości 4
- Wykonanie czynności A1 wartości 5
- Działanie A1 wykonania o wartości 6
- Działanie A1 wykonania o wartości 7
- Działanie A1 wykonania o wartości 8
- Działanie A1 wykonania o wartości 9
- Działanie A1 wykonania o wartości 10
Pokazuje to, że tylko jeden cel faktycznie wykonuje wszystkie dane, nawet gdy jest zajęty (z uwagi na celowo dodany wątek Thread.leep (100)). Dlaczego?
Dzieje się tak, ponieważ wszystkie bloki docelowe są domyślnie chciwe z natury i buforują dane wejściowe nawet wtedy, gdy nie są w stanie przetworzyć danych. Aby zmienić to zachowanie, ustawiliśmy właściwość Greedy na wartość false w DataFlowBlockOptions podczas inicjowania ActionBlock, jak pokazano poniżej.
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();
}
wyjście z tego programu jest:
- Działanie A1 wykonania o wartości 1
- Działanie A2 wykonania o wartości 3
- Działanie A1 wykonania o wartości 2
- Działanie A3 wykonującego o wartości 6
- Wykonanie czynności A3 o wartości 7
- Działanie A3 wykonania o wartości 8
- Działanie A2 wykonania o wartości 5
- Działanie A3 wykonującemu wartości 9
- Działanie A1 wykonania o wartości 4
- Działanie A2 wykonującemu wartości 10
tę wyraźnie rozkład danych według trzech bloków akcji zgodnie z oczekiwaniami.
Nie można pobrać drugiego przykładu do kompilacji. – Nathan
Nie, drugi przykład nie zostanie skompilowany z kilku powodów: Możliwe jest ustawienie tylko chciwy = false dla bloku danych "grupowania" - nie dla bloku wykonawczego; a następnie musi być ustawiony przez GroupingDataflowBlockOptions - nie DataflowBlockOptions; a następnie jest ustawiana jako wartość właściwości "{Greedy = false}", a nie parametr konstruktora.
Jeśli chcesz dławić pojemność bloku akcji, zrób to, ustawiając wartość właściwości BoundedCapacity DataflowBlockOptions (choć zgodnie z OP, są już świadomi tej opcji).Tak:
var a1 = new ActionBlock<int>(
i => doSomeWork(i),
new ExecutionDataflowBlockOptions {BoundedCapacity = 1}
);
czuję, że za pomocą BufferBlocks jest „czystszy” sposób komunikowania się pomiędzy blokami przepływu danych, ale jest na górze (jeśli występują) przy użyciu BufferBlocks warto? – Dimitri
To do ciebie należy decyzja. Jeśli uważasz, że czyni twój kod czystszym, zrób to. Ma trochę narzut, ale myślę, że nie powinno to być zauważalne, chyba że naprawdę zależy Ci na wydajności. – svick