Scale Asynchronous Client-Server Links with Reactive
On január 25, 2022 by admin- 01/31/2019
- 17 perc olvasás
June 2016
Volume 31 Number 6
By Peter Vogel | June 2016
Az aszinkron feldolgozás egyre gyakoribbá vált az alkalmazásfejlesztésben, a Microsoft .NET keretrendszer olyan eszközök széles választékát szerezte be, amelyek támogatják a konkrét aszinkron tervezési mintákat. Egy jól megtervezett aszinkron alkalmazás létrehozása gyakran az alkalmazás által megvalósított tervezési minta felismerésén, majd a megfelelő .NET-összetevők kiválasztásán múlik.
Némely esetben a párosításhoz több .NET-összetevő integrálása szükséges. Stephen Cleary cikke, “Patterns for Asynchronous MVVM Applications: Commands” (bit.ly/233Kocr) című cikkében bemutatja, hogyan lehet a Model-View-ViewModel (MVVM) mintát aszinkron módon teljes mértékben támogatni. Más esetekben a támogatáshoz csak egy komponensre van szükség a .NET Frameworkből. A szolgáltató/fogyasztó minta megvalósítását a BlockingCollection használatával a VisualStudioMagazine.com Practical .NET rovatában, a “Create Simple, Reliable Asynchronous Apps with BlockingCollection” (bit.ly/1TuOpE6) és a “Create Sophisticated Asynchronous Applications with BlockingCollection” (bit.ly/1SpYyD4) című cikkeimben tárgyaltam.
Egy másik példa a megfigyelő tervezési minta megvalósítása egy hosszú futású művelet aszinkron megfigyelésére. Ebben a forgatókönyvben egy aszinkron metódus, amely egyetlen Task objektumot ad vissza, nem működik, mivel az ügyfél gyakran eredményfolyamot ad vissza. Ezekre a forgatókönyvekre a .NET Framework legalább két eszközét használhatja ki: az ObservableCollectiont és a Reactive Extensions (Rx). Egyszerű megoldásokhoz az ObservableCollection (az async és await kulcsszavakkal együtt) elegendő. Az “érdekesebb” és különösen az eseményvezérelt problémákhoz azonban az Rx jobb kontrollt biztosít a folyamat felett.
A minta meghatározása
Míg a megfigyelő mintát gyakran használják az UI tervezési mintákban – beleértve a Model-View-Controller (MVC), Model-View-Presenter (MVP) és MVVM -, az UI-ket csak egy forgatókönyvnek kell tekinteni a forgatókönyvek nagyobb halmazából, ahol a megfigyelő minta alkalmazható. A megfigyelőminta definíciója (a Wikipédiából idézve) a következő: “Egy tárgynak nevezett objektum fenntartja függőinek, az úgynevezett megfigyelőknek a listáját, és automatikusan értesíti őket minden állapotváltozásról, általában valamelyikük metódusának meghívásával.”
A megfigyelőminta valójában arról szól, hogy a hosszú ideig futó folyamatok eredményeit az ügyfélhez juttassuk el, amint azok az eredmények rendelkezésre állnak. A megfigyelőminta valamilyen változata nélkül az ügyfeleknek meg kell várniuk, amíg az utolsó eredmény elérhetővé válik, majd az összes eredményt egyetlen csomóban kell elküldeniük. Egy egyre inkább aszinkron világban azt szeretnénk, ha a megfigyelők párhuzamosan dolgoznák fel az eredményeket az ügyféllel, amint azok elérhetővé válnak. Annak hangsúlyozása érdekében, hogy a megfigyelőminta kihasználásakor nem csak felhasználói felületről beszélünk, a cikk további részében a “megfigyelő” és a “tárgy” helyett az “ügyfél” és a “kiszolgáló” kifejezést fogom használni.
Problémák és lehetőségek
A megfigyelőmintával kapcsolatban legalább három probléma és két lehetőség van. Az első probléma a lejárt hallgatók problémája: A megfigyelőminta sok implementációja megköveteli, hogy a kiszolgáló az összes kliensére hivatkozást tartson. Ennek eredményeképpen az ügyfeleket a kiszolgáló addig tarthatja a memóriában, amíg a kiszolgáló ki nem lép. Ez nyilvánvalóan nem optimális megoldás egy hosszú ideig futó folyamat számára egy dinamikus rendszerben, ahol a kliensek gyakran csatlakoznak és szétkapcsolódnak.
A lapsed-listener probléma azonban csak egy tünete a második, nagyobb problémának: a megfigyelőminta sok implementációja megköveteli, hogy a kiszolgáló és a kliens szorosan összekapcsolódjon, megkövetelve, hogy mind a kiszolgáló, mind a kliens mindig jelen legyen. Legalább az ügyfélnek képesnek kell lennie arra, hogy megállapítsa, hogy a kiszolgáló jelen van-e, és úgy döntsön, hogy nem csatlakozik; továbbá a kiszolgálónak akkor is működnie kell, ha nincsenek eredményeket elfogadó ügyfelek.
A harmadik probléma a teljesítménnyel kapcsolatos: Mennyi időbe telik, amíg a kiszolgáló az összes ügyfelet értesíti? A teljesítményt a megfigyelő mintában közvetlenül befolyásolja az értesítendő ügyfelek száma. Ezért lehetőség van a teljesítmény javítására a megfigyelő mintában azáltal, hogy az ügyfél előzetesen megszűrheti a kiszolgálótól visszaérkező eredményeket. Ez azokat a forgatókönyveket is kezeli, amikor a kiszolgáló több eredményt (vagy az eredmények szélesebb skáláját) produkálja, mint ami az ügyfelet érdekli: Az ügyfél jelezheti, hogy csak bizonyos esetekben szeretne értesítést kapni. A második teljesítménybeli lehetőség annak felismerése körül adódik, ha a kiszolgálónak nincsenek eredményei, vagy ha befejezte az eredmények előállítását. Az ügyfelek kihagyhatják a kiszolgálói események feldolgozásához szükséges erőforrások megszerzését mindaddig, amíg az ügyfél nem tudja garantálni, hogy van mit feldolgozni, és az ügyfelek felszabadíthatják ezeket az erőforrásokat, amint tudják, hogy feldolgozták az utolsó eredményt.
A megfigyelőtől a közzététel/feliratkozásig
Az ilyen megfontolások figyelembevétele a megfigyelő minta egyszerű megvalósításaitól a kapcsolódó közzétételi/feliratkozási modellig vezet. A Publish/subscribe lazán kapcsolt módon valósítja meg a megfigyelő mintát, amely lehetővé teszi a kiszolgálók és a kliensek számára a végrehajtást akkor is, ha a másik éppen nem elérhető. A publish/subscribe jellemzően kliensoldali szűrést is megvalósít azáltal, hogy a kliens feliratkozhat bizonyos témákra/csatornákra (“Értesítsen a megrendelésekről”) vagy különböző típusú tartalmakhoz kapcsolódó attribútumokra (“Értesítsen a sürgős kérésekről”).
Egy kérdés azonban továbbra is fennáll. A megfigyelőminta minden megvalósítása hajlamos arra, hogy az ügyfeleket és a kiszolgálókat szorosan egy adott üzenetformátumhoz kösse. Az üzenet formátumának megváltoztatása a legtöbb publish/subscribe implementációban nehézségekbe ütközhet, mivel az összes klienst frissíteni kell, hogy az új formátumot használja.
Ez sok szempontból hasonlít a szerveroldali kurzor leírásához egy adatbázisban. Az átviteli költségek minimalizálása érdekében az adatbázis-kiszolgáló nem küld vissza eredményeket minden egyes sor lekérdezésekor. Nagy sorhalmazok esetén azonban az adatbázis sem adja vissza az összes sort egyetlen kötegben a végén. Ehelyett az adatbázis-kiszolgáló általában a kiszolgálón tartott kurzorból származó részhalmazokat adja vissza gyakran, amint ezek a részhalmazok elérhetővé válnak. Egy adatbázis esetében az ügyfélnek és a kiszolgálónak nem kell egyszerre jelen lennie: Az adatbázis-kiszolgáló akkor is futhat, amikor nincsenek jelen az ügyfelek; az ügyfél ellenőrizheti, hogy a kiszolgáló elérhető-e, és ha nem, eldöntheti, hogy mit (ha egyáltalán) tehet még. A szűrési folyamat (SQL) szintén nagyon rugalmas. Ha azonban az adatbázis-motor megváltoztatja a sorok visszaküldéséhez használt formátumot, akkor minden klienst legalábbis újra kell fordítani.
Objektek gyorsítótárának feldolgozása
Egy egyszerű megfigyelőminta megvalósításának vizsgálatára szolgáló esettanulmányomban szerverként egy olyan osztályt használok, amely a számlák memórián belüli gyorsítótárában keres. Ez a kiszolgáló a feldolgozás végén az összes számlát tartalmazó gyűjteményt adhatná vissza. Azonban jobban szeretném, ha a kliens egyenként és a kiszolgáló keresési folyamatával párhuzamosan dolgozná fel a számlákat. Ez azt jelenti, hogy a folyamatnak egy olyan változatát részesítem előnyben, amely minden egyes számlát a megtaláláskor visszaad, és hagyja, hogy az ügyfél minden egyes számlát a következő számla keresésével párhuzamosan dolgozzon fel.
A szerver egyszerű megvalósítása így nézhet ki:
private List<Invoice> foundInvoices = new List<Invoice>();public List<Invoice> FindInvoices(decimal Amount){ foundInvoices.Clear(); Invoice inv; // ...search logic to add invoices to the collection foundInvoices.Add(inv); // ...repeat until all invoices found return foundInvoices;}
A bonyolultabb megoldások a yield return segítségével minden egyes számlát a megtaláláskor adnak vissza, a lista összeállítása helyett. Ettől függetlenül a FindInvoices metódust hívó ügyfélnek a feldolgozás előtt és után néhány kritikus tevékenységet kell végrehajtania. Például az első elem megtalálását követően az ügyfél esetleg engedélyezni akar egy MatchingInvoices listát, hogy a számlákat az ügyfélnél tartsa, vagy megszerezze/inicializálja a számla feldolgozásához szükséges erőforrásokat. Ahogy további számlák kerülnek hozzá, az ügyfélnek minden egyes számlát fel kell dolgoznia, és amikor a kiszolgáló jelzi, hogy az utolsó számla megvan, fel kell szabadítania minden olyan erőforrást, amelyre már nincs szükség, mert “nincs több” feldolgozandó számla.
Az adatbázis lekérdezése során például az olvasás addig blokkol, amíg az első sor vissza nem érkezik. Amint az első sor visszakerül, az ügyfél inicializálja a sor feldolgozásához szükséges erőforrásokat. Az olvasás az utolsó sor lekérésekor is false-t ad vissza, így az ügyfél felszabadítja ezeket az erőforrásokat, mivel nincs több feldolgozandó sor.
Egyszerű megoldások létrehozása az ObservableCollection segítségével
A megfigyelőminta .NET Frameworkben történő megvalósításához a legkézenfekvőbb választás az ObservableCollection. Az ObservableCollection értesíti a klienst (egy eseményen keresztül), ha változik.
A mintaszerverem átírása az ObservableCollection osztály használatára mindössze két változtatást igényel. Először is, az eredményeket tartalmazó gyűjteményt ObservableCollectionként kell definiálni és nyilvánossá tenni. Másodszor, már nem szükséges, hogy a metódus eredményt adjon vissza: A kiszolgálónak csak a számlákat kell hozzáadnia a gyűjteményhez.
A kiszolgáló új implementációja így nézhet ki:
public List<Invoice> FindInvoices(decimal Amount){ public ObservableCollection<Invoice> foundInvoices = new ObservableCollection<Invoice>(); public void FindInvoices(decimal Amount) { foundInvoices.Clear(); Invoice inv; // ...search logic to set inv foundInvoices.Add(inv); // ...repeat until all invoices are added to the collection }
A kiszolgáló ezen verzióját használó ügyfélnek csak egy eseménykezelőt kell bekötnie az InvoiceManagement foundInvoices gyűjteményének CollectionChanged eseményére. Az alábbi kódban az osztállyal implementáltattam az IDisposable interfészt, hogy támogassam az eseménytől való leválást:
public class SearchInvoices: IDisposable{ InvoiceManagement invMgmt = new InvoiceManagement(); public void SearchInvoices() { invMgmt.foundInvoices.CollectionChanged += InvoicesFound; } public void Dispose() { invMgmt.foundInvoices.CollectionChanged -= InvoicesChanged; }
A kliensben a CollectionChanged esemény második paramétereként egy NotifyCollectionChangedEventArgs objektumot kap. Ennek az objektumnak az Action tulajdonsága megadja mind azt, hogy milyen változás történt a gyűjteményen (a műveletek a következők: a gyűjteményt törölték, új elemek kerültek hozzá a gyűjteményhez, meglévő elemek lettek áthelyezve/helyettesítve/eltávolítva), mind a megváltozott elemekről szóló információkat (a hozzáadott elemek gyűjteménye, a gyűjteményben az új elemek hozzáadása előtt meglévő elemek gyűjteménye, az áthelyezett/eltávolított/helyettesített elem pozíciója).
Egyszerű kód az ügyfélben, amely aszinkron módon dolgozná fel az egyes számlákat, amint azok hozzáadódnak a gyűjteményhez a kiszolgálón, úgy nézne ki, mint az 1. ábrán látható kód.
1. ábra Számlák aszinkron feldolgozása ObservableCollection használatával
private async void InvoicesFound(object sender, NotifyCollectionChangedEventArgs e){ switch (e.Action) { case NotifyCollectionChangedAction.Reset: { // ...initial item processing return; } case NotifyCollectionChangedAction.Add: { foreach (Invoice inv in e.NewItems) { await HandleInvoiceAsync(inv); } return; } }}
Bár egyszerű, ez a kód lehet, hogy nem felel meg az Ön igényeinek, különösen, ha hosszú folyamatot kezel, vagy dinamikus környezetben dolgozik. Aszinkron tervezési szempontból például a kód rögzíthetné a HandleInvoiceAsync által visszaadott Task objektumot, így az ügyfél kezelhetné az aszinkron feladatokat. Arról is gondoskodni kell, hogy a CollectionChanged esemény akkor is az UI szálon kerüljön elő, ha a FindInvoices egy háttérszálon fut.
Mert a Clear metódus meghívásának helye a kiszolgáló osztályban (közvetlenül az első számla keresése előtt) az Action tulajdonság Reset értéke használható jelzésként, hogy az első elem lekérése hamarosan megtörténik. Azonban természetesen előfordulhat, hogy a keresés során egyetlen számlát sem találunk, így a Reset Action használata azt eredményezheti, hogy az ügyfél olyan erőforrásokat rendel ki, amelyeket valójában soha nem használ fel. Az “első tétel” feldolgozásának tényleges kezeléséhez az Add Action feldolgozásához hozzá kellene adni egy olyan zászlót, amely csak akkor hajtódik végre, ha az első elemet megtalálták.
A kiszolgálónak emellett korlátozott számú lehetősége van annak jelzésére, hogy az utolsó számlát megtalálták, hogy az ügyfél ne kelljen tovább várnia a “következőre”. A kiszolgáló feltehetően törölhetné a gyűjteményt, miután megtalálta az utolsó tételt, de ez csak bonyolultabb feldolgozást kényszerítene a Reset Action feldolgozásába (Számlákat dolgoztam fel? Ha igen, akkor feldolgoztam az utolsó Számlát; ha nem, akkor most fogom feldolgozni az első Számlát).
Míg az egyszerű problémákhoz az ObservableCollection jó lesz, minden ésszerűen kifinomult, ObservableCollectionre épülő implementáció (és minden olyan alkalmazás, amely értékeli a hatékonyságot) bonyolult kódot igényel, különösen az ügyfélben.
Az Rx megoldások
Ha aszinkron feldolgozást szeretnénk, akkor a (NuGet-en keresztül elérhető) Rx jobb megoldást nyújthat a megfigyelőminta megvalósítására a publish/subscribe modell kölcsönzésével. Ez a megoldás LINQ-alapú szűrési modellt, jobb jelzést biztosít az első/utolsó elem feltételeihez és jobb hibakezelést is.
A Rx az ObservableCollectionnél érdekesebb megfigyelő implementációkat is képes kezelni. Az én esettanulmányomban a számlák kezdeti listájának visszaadása után a kiszolgálóm továbbra is ellenőrizheti azokat az új számlákat, amelyek az eredeti keresés befejezése után kerülnek a gyorsítótárba (és természetesen megfelelnek a keresési feltételeknek). Ha megjelenik egy, a kritériumoknak megfelelő számla, az ügyfél értesítést szeretne kapni az eseményről, hogy az új számla hozzáadható legyen a listához. Az Rx az ObservableCollectionnél jobban támogatja a megfigyelőminta ilyen jellegű eseményalapú kiterjesztéseit.
A megfigyelőminta támogatására két kulcsfontosságú interfész van az Rx-ben. Az első az IObservable<T>, amelyet a kiszolgáló implementál, és egyetlen metódust határoz meg: Subscribe. A Subscribe metódust megvalósító kiszolgáló egy ügyféltől kap egy objektumra való hivatkozást. A lejárt hallgatók problémájának kezelésére a Subscribe metódus egy IDisposable interfészt megvalósító objektumra vonatkozó hivatkozást ad vissza az ügyfélnek. Az ügyfél ezt az objektumot használhatja a szerverrel való kapcsolat megszakítására. Amikor a kliens megszakítja a kapcsolatot, a szerver várhatóan eltávolítja a klienst bármelyik belső listájáról.
A második az IObserver<T> interfész, amelyet a kliensnek kell implementálnia. Ez az interfész megköveteli, hogy az ügyfél három metódust valósítson meg és tegyen közzé a kiszolgáló számára: OnNext, OnCompleted és OnError. A kritikus metódus itt az OnNext, amelyet a kiszolgáló arra használ, hogy átadjon egy üzenetet az ügyfélnek (az én esettanulmányomban ez az üzenet új Invoice objektumok lennének, amelyek mindegyik megjelenésekor visszakerülnek). A szerver a kliens OnCompleted metódusával jelezheti, hogy nincs több adat. A harmadik metódus, az OnError, módot biztosít arra, hogy a kiszolgáló jelezze az ügyfélnek, hogy kivétel történt.
Az IObserver interfészt persze nyugodtan implementálhatja saját maga is (ez a .NET Framework része). Az ObservableCollectionnel együtt lehet, hogy csak erre van szükséged, ha szinkron megoldást készítesz (erről is írtam egy cikket: “Tisztább kód írása reaktív kiterjesztésekkel” ).
Az Rx azonban számos olyan csomagot tartalmaz, amelyek ezen interfészek aszinkron megvalósításait biztosítják, beleértve a JavaScript és a RESTful szolgáltatások megvalósításait is. Az Rx Subject osztály biztosítja az IObservable implementációját, amely leegyszerűsíti a megfigyelőminta aszinkron publish/subscribe verziójának megvalósítását.
Aszinkron megoldás létrehozása
A Subject objektummal dolgozó kiszolgáló létrehozása nagyon kevés változtatást igényel az eredeti szinkron szerveroldali kódban. A régi ObservableCollectiont egy Subject objektummal helyettesítem, amely minden egyes Számlát úgy ad át, ahogy az megjelenik bármelyik figyelő ügyfélnek. A Subject objektumot publikusnak nyilvánítom, hogy a kliensek hozzáférhessenek:
public class InvoiceManagement{ public IObservable<Invoice> foundInvoice = new Subject<Invoice>();
A metódus testében ahelyett, hogy egy számlát hozzáadnék egy gyűjteményhez, a Subject OnNext metódusát használom arra, hogy minden egyes számlát átadjak a kliensnek, amint megtalálja:
public void FindInvoices(decimal Amount){ inv = GetInvoicesForAmount(Amount) // Poll for invoices foundInvoice.OnNext(inv); // ...repeat...}
A kliensemben először deklarálom a szerver osztály egy példányát. Ezután egy aszinkronnak jelölt metódusban meghívom a Subject Subscribe metódusát, hogy jelezzem, hogy el akarom kezdeni az üzenetek lekérdezését:
public class InvoiceManagementTests{ InvoiceManagement invMgmt = new InvoiceManagement(); public async void ProcessInvoices() { invMgmt.foundInvoice.Subscribe<Invoice>();
Az eredmények szűréséhez, hogy csak a kívánt számlákat kapjam meg, egy LINQ utasítást alkalmazhatok a Subject objektumra. Ez a példa a számlákat a visszarendelt számlákra szűri (az Rx LINQ kiterjesztések használatához a System.Reactive.Linq névtérhez egy using utasítást kell hozzáadni):
invMgmt.foundInvoice.Where(i => i.BackOrder == "BackOrder").Subscribe();
Mihelyt elkezdtem figyelni a Subjectet, megadhatom, hogy milyen feldolgozást akarok végezni, amikor egy számlát kapok. Használhatom például a FirstAsync-t, hogy csak a szolgáltatás által visszaküldött első számlát dolgozzam fel. Ebben a példában az await utasítást használom a FirstAsync hívásával együtt, így a számla feldolgozása közben visszaadhatom a vezérlést az alkalmazásom fő részének. Ez a kód megvárja, hogy lekérje az első számlát, majd áttér arra a kódra, amelyet a számla feldolgozási folyamatának inicializálásához használok, és végül feldolgozza a számlát:
Invoice inv;inv = await invMgmt.foundInvoice.FirstAsync();// ...setup code invoices...HandleInvoiceAsync(inv);
Egy figyelmeztetés: a FirstAsync blokkolni fog, ha a kiszolgáló még nem adott eredményt. Ha el akarja kerülni a blokkolást, használhatja a FirstOrDefaultAsync-et, amely nullát ad vissza, ha a kiszolgáló még nem adott eredményt. Ha nincsenek eredmények, az ügyfél eldöntheti, hogy mit tegyen, ha egyáltalán tesz valamit.
A tipikusabb eset az, hogy az ügyfél az összes visszaküldött számlát (szűrés után) aszinkron módon szeretné feldolgozni. Ebben az esetben a Subscribe és az OnNext kombinációja helyett egyszerűen a ForEachAsync metódust használhatjuk. Átadhat egy metódust vagy egy lambda-kifejezést, amely feldolgozza a bejövő eredményeket. Ha átadsz egy metódust (ami nem lehet aszinkron), ahogy itt teszem, akkor annak a metódusnak átadod a ForEachAsync-t kiváltó számlát:
invMgmt.foundInvoice.ForEachAsync(HandleInvoice);
A ForEachAsync metódusnak átadhatsz egy cancellation tokent is, hogy a kliens jelezze, hogy megszakítja a kapcsolatot. Jó gyakorlat lenne a token átadása bármelyik Rx *Async metódus hívásakor, hogy támogassa, hogy az ügyfél befejezhesse a feldolgozást anélkül, hogy meg kellene várnia az összes objektum feldolgozását.
A ForEachAsync nem fogja feldolgozni a First (vagy FirstOrDefaultAsync) metódus által már feldolgozott eredményeket, így a FirstOrDefaultAsync-et a ForEachAsync-el együtt használhatja, hogy ellenőrizze, hogy a szervernek van-e feldolgoznivalója a további objektumok feldolgozása előtt. A Subject IsEmpty metódusa azonban egyszerűbben elvégzi ugyanezt az ellenőrzést. Ha az ügyfélnek ki kell osztania az eredmények feldolgozásához szükséges erőforrásokat, az IsEmpty lehetővé teszi az ügyfél számára, hogy ellenőrizze, hogy van-e valami tennivaló, mielőtt kiosztja ezeket az erőforrásokat (egy alternatíva az lenne, ha ezeket az erőforrásokat a ciklusban feldolgozott első elemnél osztaná ki). Az IsEmpty használata egy olyan klienssel, amely az erőforrások kiosztása (és a feldolgozás megkezdése) előtt ellenőrzi, hogy vannak-e eredmények, miközben támogatja a törlést is, a 2. ábrához hasonló kódot eredményezne.
2. ábra A törlést támogató és a feldolgozást az eredmények elkészültéig elhalasztó kód
CancellationTokenSource cancelSource = new CancellationTokenSource();CancellationToken cancel;cancel = cancelSource.Token;if (!await invMgmt.foundInvoice.IsEmpty()){ // ...setup code for processing invoices... try { invMgmt.foundInvoice.ForEachAsync(HandleInvoice, cancel); } catch (Exception ex) { if (ex.GetType() != typeof(CancellationToken)) { // ...report message } } // ...clean up code when all invoices are processed or client disconnects}
Összefoglalás
Ha csak a megfigyelőminta egyszerű megvalósítására van szükségünk, akkor az ObservableCollection talán elegendő az eredményfolyam feldolgozásához. A jobb irányításhoz és egy eseményalapú alkalmazáshoz a Subject osztály és az Rx-hez mellékelt bővítmények lehetővé teszik, hogy az alkalmazás aszinkron üzemmódban működjön a publish/subscribe modell erőteljes megvalósításának támogatásával (és még nem néztem meg az Rx-hez mellékelt operátorok gazdag könyvtárát). Ha Rx-szel dolgozol, érdemes letölteni az Rx Design Guide-ot (bit.ly/1VOPxGS), amely a megfigyelhető adatfolyamok fogyasztásának és előállításának legjobb gyakorlatait tárgyalja.
A Rx némi támogatást nyújt a kliens és a szerver között átadott üzenettípus átalakításához is az ISubject<TSource, TResult> interfész használatával. Az ISubject<TSource, TResult> interfész két adattípust határoz meg: egy “in” és egy “out” adattípust. Az ezt az interfészt megvalósító Subject osztályon belül elvégezhetünk minden olyan műveletet, amely ahhoz szükséges, hogy a kiszolgálótól visszakapott eredményt (a “be” adattípus) az ügyfél által igényelt eredménnyé (a “ki” adattípus) alakítsuk át. Továbbá az in paraméter kovariáns (elfogadja a megadott adattípust vagy bármit, amitől az adattípus örököl), az out paraméter pedig kontravariáns (elfogadja a megadott adattípust vagy bármit, ami tőle származik), ami további rugalmasságot biztosít.
Egyre inkább aszinkron világban élünk, és ebben a világban a megfigyelő minta egyre fontosabbá válik – hasznos eszköz minden olyan folyamatok közötti interfészhez, ahol a kiszolgáló folyamat nem egyetlen eredményt ad vissza. Szerencsére a .NET Frameworkben számos lehetőség áll rendelkezésre a megfigyelőminta megvalósítására, köztük az ObservableCollection és az Rx.
Peter Vogel rendszerarchitekt és a PH&V Information Services igazgatója. A PH&V teljes körű tanácsadást nyújt a UX-tervezéstől az objektummodellezésen át az adatbázis-tervezésig.
Köszönjük a Microsoft következő műszaki szakértőinek a cikk felülvizsgálatát: Stephen Cleary, James McCaffrey és Dave Sexton
Stephen Cleary 16 éve foglalkozik többszálú és aszinkron programozással, és az első közösségi technológiai előzetes óta használja a Microsoft .NET Framework aszinkron támogatását. Ő a szerzője a “Concurrency in C# Cookbook” (O’Reilly Media, 2014) című könyvnek. Honlapja, beleértve a blogját is, a stephencleary.com címen található.
Ez a cikk az MSDN Magazine fórumában
vitatható meg.
Vélemény, hozzászólás?