Klasa Barrier umożliwia synchronizacje kilku wątków. Mechanizm bardzo znany np. z Ady pozwala dopuścić wykonanie kodu wyłącznie jeśli inne wątki na to się zgadzają. Załóżmy, że mamy 4 wątki robiące równoległe jakieś obliczenia. Po ich zakończeniu chcemy scalić wynik ale musimy poczekać aż wszystkie zadania zostaną ukończone. W C# 4.0 można wykorzystać tzw. taski, ale w tym artykule skupimy się na Barrier, która służy do dużo bardziej skomplikowanych zadań.
Przypuśćmy, że nasz algorytm składa się z kilku faz – z tym że faza następna nie może być rozpoczęta dopóki wszystkie składowe fazy A nie zostały skończone. Zatem w fazie A, 4 wątki wykonują operacje, po ich zakończeniu rozpoczynamy fazę B itp. Nie możemy jednak rozpocząć fazy B dopóki faza A nie zostanie skończona przez wszystkie wątki.
Definiujemy więc barierę dla 4 uczestników:
Barrier barrier = new Barrier(4, (b) => { MessageBox.Show(string.Format("Wynik={0}, faza={1}", magicNumber, b.CurrentPhaseNumber)); });
Pierwszy parametr to liczba uczestników (wątków) a drugi to callback wyświetlający numer właśnie zakończonej fazy oraz wyliczoną wartość w algorytmie. Każdy wątek zawiera taką samą logikę liczenia liczby:
Action calculations = () => { Interlocked.Increment(ref magicNumber); barrier.SignalAndWait(); // faza A zakończona Interlocked.Increment(ref magicNumber); barrier.SignalAndWait(); // faza B zakończona Interlocked.Increment(ref magicNumber); barrier.SignalAndWait(); // faza C zakończona Interlocked.Increment(ref magicNumber); barrier.SignalAndWait(); // faza D zakończona };
SignalAndWait wysyła sygnał do innych wątków oraz czeka (blokuje) aż inne wątki wyślą również sygnał. Jeśli zostanie uruchomionych więcej wątków niż liczba uczestników, Barrier wyrzuci wyjątek:
The number of threads using the barrier exceeded the total number of registered participants.
Następnie wystarczy uruchomić 4 wątki:
Parallel.Invoke(calculations, calculations, calculations, calculations);
Dobrym zwyczajem jest również wywołanie Dispose na Barrier:
barrier.Dispose();
Istnieje również możliwość zmieniania liczby uczestników już po inicjalizacji bariery:
barrier.AddParticipants(2); barrier.RemoveParticipant();
Przykładowy kod kolejno powinien wyświetlić liczby 4,8,12,16. Mamy tego pewność ponieważ następna faza nie zostanie rozpoczęta dopóki poprzednia się nie zakończy a na koniec każdej fazy wynik zwiększa się o 4.
Podobnie wykorzystać można WaitHandle posiada ona dwie fajne metody WaitAll i WaitAny. W praktyce (nieakademickiej) nie użyłem jej jeszcze ani raz.
@wojtek:
Barrier raczej w bardziej “skomplikowanych” aplikacjach wielowatkowych przyda sie. W prostych, Monitor wystarcza w zupelnosci. A w webowych to juz sprawa kompletnie sie upraszcza(przewaznie)…