Přejít k navigační liště

Zdroják » Různé » Úvod do Reactive Extensions

Úvod do Reactive Extensions

Články Různé

Asynchrónne programovanie sa v súčasnosti dostáva stále viac do pozornosti, pomaly sa stáva nutnosťou a budeme sa s ním stretávať stále častejšie. Napríklad vo Windows Phone 7 je dobrým zvykom vykonávať náročné operácie asynchrónne v osobitnom vlákne, aby UI aj naďalej reagovalo na vstup používateľa, vo Windows 8 je dokonca nutné všetky operácie, ktoré môžu trvať dlhšie ako 50 milisekúnd, vykonávať asynchrónne.

Programovať asynchrónne však nie je vôbec jednoduché a programátor okrem väčšej komplexnosti čelí aj problémom ako race conditions a pod. Našťastie existujú knižnice, ktorých cieľom je uľahčiť programovanie v asynchrónnom svete. Jednou z takýchto knižníc je Reactive Extensions pre .NET.

Reactive Extensions

Reactive Extensions (ďalej Rx) je knižnica pre .NET, ktorá zavádza trochu iný pohľad na prácu s dátami, najmä kolekciami a udalosťami. Aktuálne je Rx dostupné pre nasledujúce .NET platformy:

  • .NET Framework 3.5 SP1
  • .NET Framework 4
  • Silverlight 4
  • Silverlight 5
  • Windows Phone 7
  • Windows Phone 7.1

Podľa popisu na webe projektu je to „library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators.“, teda knižnica na vytváranie programov založených na asynchrónných operáciách a udalostiach pomocou kolekcií implementujúcich IObservable<T> s plnou podporou LINQ operátorov.

Pre lepšie pochopenie Rx musíme najprv pochopiť pojmy asynchrónne programovanie, LINQ a návrhový vzor Observer.

Asynchrónne programovanie

Pojem asynchrónne programovanie by mal byť jasný každému programátorovi, nezaškodí však malé pripomenutie. Klasický program v imperatívnom programovacom jazyku pozostáva zo sledu príkazov, ktoré sú vykonávané postupne jeden za druhým. Ak teda zavoláme metódu A a potom metódu B, metóda B bude vykonaná až potom, čo metóda A skončí. Metóda B môže využívať výsledok metódy A, môže sa spoľahnúť na to, že v čase svojho spustenia bude dostupný.

Pri asynchrónnom programovaní táto podmienka sekvenčnosti vykonávania príkazov neplatí. Ak je metóda A asynchrónna, jej zavolaním sa rozbehne nové vlákno (proces), ktoré je zodpovedné za jej vykonanie a tok programu pokračuje vykonávaním metódy B. V závislosti na tom, ako dlho vykonanie metódy A trvá, môže byť metóda B vykonaná pred, po, alebo počas vykonávania metódy A a nemôže sa spoliehať na dostupnosť jej výsledku. Programovací jazyk typicky obsahuje možnosť, ako sa o skončení metódy A dozvedieť, najčastejšie ide o udalosti (events).

LINQ

Skratka LINQ znamená Language INtegrated Query a predstavuje rozšírenie .NET (od 3.0) o možnosť dotazovania a transformácie kolekcií, relačných dát a XML dokumentov. LINQ v podstate umožňuje dotazovanie podobné SQL nad dátami bez ohľadu na to, odkiaľ pochádzajú.

Ďalším podstatným znakom LINQ je, že do .NET prináša viac deklaratívny (funkcionálny) prístup k programovaniu. Na rozdiel od klasického imperatívneho programovania sa pri použití LINQ sústredíme na to, čo chceme dosiahnuť a nie na to, ako to dosiahnuť.

Predstavme si, že chceme z nejakej kolekcie vybrať všetky objekty spĺňajúce nejakú podmienku. V imperatívnom prístupe musíme rozmýšľať o tom, ako to dosiahnuť, teda o vytvorení novej kolekcie na uloženie výsledku, prechode prvkov foreach cyklom, teste objektov na danú podmienku a pridávaní prvkov do novej kolekcie.

Kód pre získanie párnych čísiel by mohol vyzerať takto:

List<int>  numbers= Enumerable.Range(1,10);
List<int> evenNumbers= new List<int>();
    
foreach (int  number in numbers)
{
    if (number % 2 ==0)
    {
        evenNumbers.Add(number)
    }
}

Enumerable.Range je funkcia, ktorej zadáme počiatočné číslo a počet čísel a vygeneruje daný interval.

V prípade LINQ a deklaratívneho programovania sa naozaj sústredime len na to, čo chceme vykonať, teda vybrať objekty spĺňajúce podmienku do novej kolekcie

List<int>  numbers= Enumerable.Range(1,10);
List<int> evenNumbers = (from number in numbers where number %% 2 == 0 select number).ToList();

LINQ umožňuje používať aj alternatívnu syntax, v ktorej sú jednotlivé operátory volané ako funkcie

List<int>  numbers= Enumerable.Range(1,10);
List<int> evenNumbers = numbers.Where(number => number % 2 == 0).Select(number => number).ToList();

Návrhový vzor Observer

Observer je návrhový vzor, v ktorom si objekt zvaný subjekt uchováva zoznam závislých objektov, tzv. observerov, ktorých informuje o zmenách svojich vlastností, najčastejšie volaním ich metód.

Príkladom dátovej štruktúry implementujúcej návrhový vzor Observer v .NET môže byť ObservableCollec­tion používaná v Silverlighte a Windows Phone 7. Ak túto kolekciu (subjekt) napojíme napríklad na listbox (observer) a pridávame do nej prvky, ObservableCollec­tion automaticky oznámi listboxu, že v nej došlo k zmene (pridanie, odobranie, update prvku) a listbox sa automatický prekreslí tak, aby túto zmenu zohľadnil.

Push vs Pull model

Princíp Rx sa dá asi najlepšie vysvetliť práve na kolekciách. Všetky kolekcie v .NET implementujú rozhranie IEnumerable<T> a dáta z nich „vyťahujeme“. Napríklad pri použití foreach cyklu na kolekciu je najprv nutné získať iterátor pomocou GetEnumerator, následne je volaná metóda MoveNext, ktorá určuje, či sa v kolekcií nachádza ešte nejaký ďalší prvok. Ak áno, je dostupný pomocou vlastnosti Current. Tento model sa nazýva pull model (pull = vyťahovať).

Foreach cyklus z predchádzajúceho príkladu vyzerá v skutočnosti tak­to:

List<int>  numbers= Enumerable.Range(1,10);
List<int> evenNumbers= new List<int>();
    
var e= numbers.GetEnumerator();
while (e.MoveNext())
{
    int number = e.Current;
    if (number % 2 ==0)
    {
        evenNumbers.Add(number)
    }
}

Dátový model LINQ to Rx je matematický dúalny model k modelu IEnumerable<T>. Namiesto toho, aby sme prvky z kolekcie vyťahovali, kolekcia nám ich sama posiela. Ide o tzv. push model. Základom tohto modelu sú rozhrania IObservable<T> a IObserver<T>, ktoré sú súčasťou .NET 4.0 a sú duálne k IEnumerable<T> a IEnumerator<T>.

S push modelom sa stretávame napríklad pri udalostiach v UI. Klasický button nám posiela udalosť Click pri každom svojom stlačení. Iným príkladom môžu byť asynchrónne volania webových služieb. Jedným z cieľov Rx je práve zjednotiť a hlavne zjednodušiť tieto operácie.

LINQ to Rx

Použitie LINQ to Rx je podobné použitiu udalostí. K IObservable<T> kolekcii je najprv potrebné sa naviazať pomocou metódy Subscribe (obdoba += pri udalostiach). Pomocou rozhrania IObserver<T> sú nám následne vystavené udalosti OnNext, OnCompleted a OnError, na ktoré môžeme reagovať.

Kolekcia nám bude posielať dáta, ktoré môžeme spracovávať po vyvolaní OnNext. Koniec kolekcie je oznámený udalosťou OnCompleted a chyba udalosťou OnError.

Pre lepšiu predstavu uvádzam diagram, ktorý ukazuje, ako funguje foreach v oboch (pull a push) modeloch.

Obrázok 1: Sekvenčný diagram pull a push modelu (prebrané z knihy C# in depth od Joa Skeeta)

Jednoduchý príklad

Rx implementuje väčšinu LINQ operátorov a podporuje reťazenie, modifikovaný predchádzajúci príklad (vypisuje párne čísla, nevkladá ich do zoznamu) by v push modeli vyzeral takto:

var numbers= Observable.Range(0, 10);
numbers.Where(number=> number & 2 == 0).Subscribe(
    x  => Console.WriteLine("OnNext:  {0}", x),
    ex => Console.WriteLine("OnError: {0}", ex.Message),
    () => Console.WriteLine("OnCompleted"))

Observable.Range je push obdoba Enumerable.Range s rovnakými parametrami.

Použitie ďalších LINQ operátorov spolu s Rx je podobné, napríklad klasický Select použijeme tak, že vytvoríme query a následne sa na danú query naviažeme:

var numbers = Observable.Range(0, 10);
var query = from number in numbers
            where number % 2 == 0&
            select number * number;

query.Subscribe(Console.WriteLine);

Možno vás napadla otázka, kedy vlastne začne kolekcia odosielať dáta. Záleží to od typu kolekcie. Rozlíšujeme tzv. hot a cold obervables.  Cold observables začnú posielať dáta ihneď po naviazaní (Subsrcibe), príkladom je použité Range. Hot observables posielajú prvky, aj keď na nich nie je nikto naviazaný, príkladom môžu byť udalosti myši.

Praktický príklad

Doteraz uvedené príklady nemali z praktického hľadiska veľké využitie a pravdepodobne nikoho nepresvedčilo, aby začal Rx používať. Ukážeme si preto praktické použitie vo Windows Phone 7.

Predstavme si nasledujúci scénar. Vo Windows Phone 7 aplikácií máme TextBox, do ktorého používateľ píše text, ktorý chce vyhľadať na Wikipédií. Pod TextBoxom je komponenta WebBrowser, ktorá nájdenú stránku Wikipédie zobrazuje. Vzhľadom na dobrú použiteľnosť chceme výsledky vo Wikipédií zobrazovať nie po každom zadanom znaku ale až v momente, keď používateľ prestane na chvíľu písať.

Bez Rx by bolo potrebné reagovať na každú udalosť KeyUp textboxu, použiť nejaký timer na meranie pauzy medzi týmito udalosťami a kód by bol veľmi komplexný.

V Rx (za predpokladu, že daný TextBox sa volá Search) sa najprv naviažeme na udalosti KeyUp

var keys = Observable.FromEvent<KeyEventArgs>( Search, "KeyUp" )

A použijeme operátor Throttle

var keys = Observable.FromEvent<KeyEventArgs>(  Search, "KeyUp" ) .Throttle( TimeSpan.FromSeconds( 0.5 ) );

Kolekcia keys nám teraz bude vďaka Throttle posielať udalosti len v prípade, že používateľ na pol sekundy zastaví písanie. Jednoduché, elegantné a hlavne dobre čitateľné.

Ďalšou dôležitou vlastnosťou okrem toho, že operátory je možné ľubovoľne spájať a kombinovať je fakt, že Observables sú tzv. first class citizens. Znamená to výrazne univerzálnejšie a jednoduchšie použitie, pretože

  • Môžu byť uložené v premenných a dátových štruktúrach
  • Môžu byť použité ako parametre metód
  • Možu byť použité ako návratové hodnoty metód
  • Môžu byť vytvorené za behu
  • Majú skutočnú hodnotu nezávislú na názve

Záver

Cieľom tohto článku bolo oboznámenie sa s Rx, push modelom a ukážka praktického použitia. Rx je zaujímavý hlavne v tom, že núti programátora  uvažovať v trochu inej rovine, ako bol doteraz zvyknutý Je to však silný nástroj, ktorého pochopením a používaním je možné zjednodušiť si prácu s asynchrónnymi operáciami.

Komentáře

Subscribe
Upozornit na
guest
15 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
blizz

Aha takze aky rozdiel je medzi tymto

var nums = Enumerable.Range(0, 10);
nums.Where(number => number % 2 == 0).ToList().Fo­rEach(x => Console.Write­Line(x));

a tymto:

var numbers = Observable.Range(0, 10);
numbers.Where(num­ber => number % 2 == 0).Subscribe(x => Console.Write­Line(x));

v prvom pripade sa najprv vyberu vsetky data z kolekcie a odfiltruju a potom prejdu a vypisu, v druhom pripade sa vysledky vramci filtrovania vypisuju priebezne?

Aleš Roubíček

Rozdíl ani tak není ve filtrování – v obou případech probíhá lazy, ale v tom, že druhý běží asynchroně a je nonblocking.

blizz

Takze zjednodusene povedane v druhom pripade zakladne vlakno aplikacie, prejde na dalsi riadok, zatial co hodnoty sa vypisuju na inom vlakne az vo chvili ked ich mame k dispozicii?

Friv

Pre klasické aplikácie to samozrejme neplatí.

xxx

4. 7. 2012 7:49 redakčně upravil Martin Hassman, důvod: Link spam.
Rene Stein

Díky za osvětový článek v češtině o RX!

Jen pár poznámek.

Cituji:
„Pri asynchrónnom programovaní táto podmienka sekvenčnosti vykonávania príkazov neplatí. *Ak je metóda A asynchrónna, jej zavolaním sa rozbehne nové vlákno (proces)*, ktoré je zodpovedné za jej vykonanie a tok programu pokračuje vykonávaním metódy B.“

Jsem zvědav, kdy nad tímhle někdo vypění.:) Asynchronní != (nutně) paralelní.

Mohl bych poprosit, co znamená, nebo co si představujete pod tím, že „Observables sú tzv. first class citizens“?

Tento termín se ve Vámi uváděném kontextu většinou používá u delegátů (a higher order functions, což jsou mj. skoro všechny operátory v RX)

Observables jsou prostě objekty podporující rozhraní IObservable – a rozhraní je samozřejmě „first class citizen“, ale proč to zdůrazňovat.

Kdyby někoho zajámalo, jak se dá třeba RX použít k lepší práci s nešikovným EAP (Event-based Asynchronous Pattern) vzorem v .Net, kdy asynchronní kód lze psát skoro stejně jako kód synchrnonní i bez klíčového slova await v C# 5,tak ho odkážu (self promo, omlouvám se) na svůj článek (z roku 2010)
http://jdem.cz/q32u4

backup

….Windows 8 je dokonca nutné všetky operácie, ktoré môžu trvať dlhšie ako 50 milisekúnd, vykonávať asynchrónne. …

ja jsem vzal program, je prelozeny na win2000 a spustil jsem ho pod w8 a fungovalo to. Znamena to, ze se nahodou nikde nenasla nejaka operace delsi nez 50 milisekund nebo nema autor pravdu.

Co je to operace?

Martin Hassman

Tak o tom limitu 50 ms se poměrně dost píše, čirý výmysl to asi nebude 8-) Otázka je, co se přesně stane, podle http://www.mindscapehq.com/blog/index.php/2012/03/13/asynchronous-programming-in-c-5/ po 50 ms OS nebude čekat na návratovou hodnotu akce/operace/u­dálosti/api volání a poběží dál.

Jsem zvědav, jak přísně to bude řešené a zda se z důvodu zpětné kompatibility stejně nezavedou nějaké výjimky.

Rene Stein

To, co jste citoval, platí pro *návrh* API Windows RT aplikací.

Jestliže operace může trvat déle než 50 ms, má být pro ni vystaveno asynchronní API.

Tedy:

1) Vy jsem měl aplikaci, která je součástí tzv. klasického desktopu – nešlo o Windows RT aplikaci.

2) 50 ms na operaci je doporučení/po­žadavek/design guideline/ na autory metod ve Windows RT aplikacích. Neznamená to, že nějaká starší WIN32 API v klasickém desktopu nebo nové metody ve Windows RT budou zázračně a a samy od sebe najednou asynchronní (ani nebudou „jen tak“ zázrakem paralelizovány).

Rene Stein

A pro martina Hassmnana:
Nevím, do jaké míry je něco takového u magazínu v ČR možné, ale už před nějakou dobou jsem se na G+ ptal, jestli někdo na Zdrojáku dělá oponenturu odborných článků.

https://plus.google.com/100048458679392044814/posts/BqUVFYfkTtb

Martin Hassman

Ne, ve vydávacím procesu oponentury nemáme a nevím o žádném e-magazínu z oblasti programování (a vlastně IT obecně), který by něco takového v ČR dělal. (Pokud někdo o takovém víte, rychle sem s ním!) U média s kadencí textů podobnou Zdrojáku to považuji v ČR za nemožné (za současné ekonomické situace, jaká v médiích panuje).

Zdroják není „odborný recenzovaný časopis“, kde je něco takového zvykem, resp. požadavkem. Autor za svůj text zodpovídá, redakce pak zodpovídá za výběr autora, snaží se ho prověřit, a když jsme na pochybách (a to občas jsme), tak se pokusíme oponenturu zajistit, je-li tedy na dané téma od koho. Tím dosáhneme nějaké úrovně minimalizace problémů, kterou jsme ještě schopní realizovat. Nemyslím, že by to šlo dělat nějak výrazně lépe, ale pokud o nějakém funkčním a ověřeném modelu někdo víte, rád se nechám inspirovat.

A pokud jsem v té otázce vycítil správně, že byste měl zájem občas nějaké Windows/.NET texty oponovat, tak s tím rozhodně nemám problém, naopak, jen nemůžu nabídnout žádný honorář.

Rene Stein

;) Tomu říkám pohotový přístup.:)

Ne, na oponenturu jsem se nenabízel, ani zadarmo, ani za peníze, jestliže to v příspěvku i nepřímo bylo, onmlouvám se za ty iluze. :)

Spíš mě v tom příspěvku na G+ zajímalo, jestli se něco takového dělí, protože chápu, že šéfredaktor nemůže znát vše, ale za vše musí zodpovídat.
Ty ekonomické důvody samozřejmě chápu.

Rene Stein

„Spíš mě v tom příspěvku na G+ zajímalo, jestli se něco takového dělá“.

Omlouvám se i za další překlepy, psáno na mobilu.

Martin Hassman

V naprosté většině případů rozumí autor textu mnohem líp než šéfredaktor (kdyby to bylo naopak, psal bych si ty texty radši sám 8-).

Ta odpovědnost je sdílená. Autor je primárně zodpovědný za odbornou stránku textu (je to větší odborník), šéfredaktor za výběr témat, editor potom (v našem případě je šéfredaktor a editor tatáž osoba) za srozumitelnost a čtivost textu (většinou je totiž větším odborníkem na psaní než autor). Spojení autora + šéfredaktora by mělo mít synergický efekt (jinak by to nefungovalo a můžeme to zavřít).

To ale neznamená, že tam vůbec žádný odborný filtr není. Je. A dokonce velmi levný. Objeví se totiž autoři, kteří se neosvědčí, a k peru je dále nepustíme nebo se objeví takoví, na které je třeba zvýšený dohled, případně takoví, kteří si nějakou formu zpětné kontroly (neříkám rovnou oponentury) zajistí před zasláním sami, protože se třeba jejich jméno stává dobrou „značkou“, na které si musí nechat záležet.

Většina problémů se tímhle způsobem vyřeší a srovná, jen se to děje metodou crowdsourcingu, tj. po publikaci za využití „levné síly“ čtenářů (viz ostatně tohle vlákno), nikoliv nějakým placeným týmem expertů na straně redakce. Nejedná o nějaké bizarní znásilňování čtenářů, ona řada z nich na sebe tuhle roli bere velmi ráda, ale to už bychom se přesunuli spíš do oblasti psychologie.

Chtěl jsem jen ukázat, že ten stávající mechanismus není bezchybný, ale je docela dobře funkční. O síle onoho „docela dobře“ bychom jistě mohli dlouho diskutovat.

igorkulman

Uviedol som to nepresne, sú tým myslené Metro aplikácie. Pre klasické aplikácie to samozrejme neplatí.

msx

V Androide to, napríklad, funguje tak, že pokiaľ nejaká činnosť trvá viac ako 2 sekundy, tak systém automaticky otvorí okno s možnosťou zatvoriť aplikáciu, ktorá neodpovedá. Toto isté môže byť aj vo Windows 8.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.