W ramach serii postów dotyczących Reactive Extensions przyjrzeliśmy się już problemom związanych z asynchronicznością oraz przyjrzeliśmy się bliżej kolekcjom i wzorcu obserwatora (IObserver, IObservable). W tym wpisie zobaczymy pierwszy przykład kodu wykorzystującego RX.
Jak wspominałem wcześniej, w .NET 4.0 jest wbudowane pewne wsparcie dla IObserver i IObservable. Jednak są to tylko definicje wspomnianych interfejsów, aby wykorzystać pełne możliwości RX, trzeba je zainstalować i dołączyć do projektu, widać to na
poniższym rysunku:
O pobieraniu i instalacji RX pisałem już wcześniej i najłatwiej to chyba zrobić przy
pomocy NuGet'a. Wpisujemy „rx” do wyszukiwarki, czekamy chwilę i
już możemy instalować:
Koniecznie należy zainstalować „Rx-Main”, ale przydać się
mogą i inne biblioteki. Np. jeżeli tworzymy aplikację
wykorzystującą WPF, to warto doinstalować również „Rx-WPF”
(by wygodnie obserwować na wątku Dispatcher'a).
Po instalacji RX znajdziemy w projekcie referencję do assembly
„System.Reactive”, w której znajdziemy wiele interesujących
rozszerzeń i klas stanowiących siłę RX:
Przejdźmy jednak do obiecywanego kodu. Zacznijmy od bardzo prostego
przykładu, w którym prostą tablicę przekształcimy na IObservable
i wypiszemy jej zawartość:
using System; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; namespace RXExamples { class Program { static void Main( string[] args ) { var char_array = "Hello world!!".ToArray(); IObservable<char> obs = char_array.ToObservable( ); using ( obs.Subscribe( Console.WriteLine ) ) { Console.WriteLine( "Hit Enter to finish" ); Console.ReadLine(); } } } }
W tym przykładzie zamieniliśmy tablicę znaków z napisem „Hello
world!!” na IObservable przy użyciu funkcji ToObservable.
Następnie zasubskrybowaliśmy nasz obiekt typu IObservable (funkcja
Subscribe) i jako parametr przekazaliśmy Console.WriteLine, czyli
delegację wypisującą otrzymywane znaki na okno konsoli. W wyniku
działania powyższego kodu otrzymamy:
H e l l o w o r l d ! ! Hit Enter to finish
Oczywiście nie ma tu nic niezwykłego, ktoś mógłby stwierdzić,
że to tylko trudniejsze wypisanie wszystkich elementów tablicy.
Prosta jednowątkowa aplikacja, ale.... wystarczy dopisać tylko
prosty element, by obserwacja odbywała się na osobnym wątku. W tym
celu można wykorzystać metodę rozszerzającą ObserveOn lu
przekazać pewien parametr do metody ToObservable. Wspomnianym
parametrem może być np. Scheduler.NewThread. W ten sposób
wykonując poniższy kod:
static void Main( string[] args ) { var char_array = "Hello world!!".ToArray(); IObservable<char> obs = char_array.ToObservable( Scheduler.NewThread ); using ( obs.Subscribe( Console.WriteLine ) ) { Console.WriteLine( "Hit Enter to finish" ); Console.ReadLine(); } }
Tym razem otrzymamy:
Hit Enter to finish H e l l o w o r l d ! !
Jak widać tym razem napis „Hit Enter to finish” pojawił się
przed wypisaniem znaków z obserwowanej tablicy, a znaki z tablicy
zostały wypisane w osobnym wątku. Można to również sprawdzić
debugując aplikację i przeglądając watki:
Jak widać, subskrypcja odbywa się na osobnym wątku (Worker
Thread), w odróżnieniu o wątku aplikacji (Main Thread).
Przedstawiony przykład może wydawać się prosty i banalny, ale to
dopiero początek ... :)
cdn...
Ciekawy tekst. Tylko jest moment którego nie rozumiem, chodzi o:
OdpowiedzUsuńusing ( obs.Subscribe( Console.WriteLine ) )
{
Console.WriteLine( "Hit Enter to finish" );
Console.ReadLine();
}
<- zgodnie z opisem, argumentem Subscribe powinien być instancja obiektu dziedziczącego po interfejsie IObserver - tymczasem jako argument Subscribe podajemy ... delegat na funkcję Console.WriteLine
O co tu chodzi? :)
I jak to się dzieje ze ReadLine odczytuje wartości które wcześniej zostały wpisane przez WriteLine?
Cześć,
OdpowiedzUsuńZgadza się zgodnie z poprzednim postem (w którym opisywany był wzorzec obserwator) argumentem dla Subscribe powinna być instancja obiektu dziedziczącego po interfejsie IObserver i właśnie taka jest definicja Subscribe dla IObservable. RX pozwala jednak na pewne uproszczenia, otóż subskrybując można przekazać do Subscribe również delegat do obsługi OnNext lub nawet wszystkie trzy delegaty: OnNext, OnError, OnCompleted. Dzięki takiemu podejściu nie musimy sami tworzyć IObserver, a zostanie to zrobione "za nas", na podstawie przekazanych delegat.
Jeżeli chodzi natomiast o "ReadLine, które odczytuje wartości wypisane wcześniej przez WriteLine", to nic takiego nie ma tu miejsca. "ReadLine" jest tu tylko po to, żeby oczekiwać na interakcję ze strony użytkownika, a w szczególności naciśnięcie klawisza Enter, który zakończy tą aplikację.
Mam nadzieję , że to trochę rozwiało wątpliwości.
Rozumiem, czyli Console.WriteLine jest tu delegatem do obsługi OnNext.
OdpowiedzUsuńTo wszystko wyjaśnia. Dzięki za info.
Muszę sobie ten przykład przerobić w wolnej chwili.
Więcej przykładów dostępnych jest też we wpisie: [RX 6] Przykłady dot. obserwowalnych kolekcji w Reactive Extensions.
OdpowiedzUsuń