'Y'

Procesarea taskurilor prin Completion Idiom

Atunci cand lucram cu taskuri, apare de foarte multe ori urmatoare provocare: avem un set de date ca input si pentru procesarea fiecarui element folosim long-running operations. Insa putem aborda aceasta sarcina mai direct - ruleaza bucla, lanseasa taskul si proceseaza rezultatele unul cate unul:

Feb 15, 2016 2333
Atunci cand lucram cu taskuri, apare de foarte multe ori urmatoare provocare: avem un set de date ca input si pentru procesarea fiecarui element folosim long-running operations. Insa putem aborda aceasta sarcina mai direct - ruleaza bucla, lanseasa taskul si proceseaza rezultatele unul cate unul:

Completion_Idiom_1.jpg

Spre exemplu, avem in aceasta situatie un serviciu meteo pe care il folosim pentru a obtine temperatura intr-un anumit numar de orase si apoi, in urma procesarii rezultatelor, sunt afisate numele oraselor si temperatura pe ecran.

Abordarea merge, dar exista o problema: noua sarcina va fi lansata doar dupa ce a fost terminata sarcina anterioara. Poti sa fortezi finalizarea tuturor sarcinilor si sa apelezi ToList() via LINQ request, dar in acesta caz sarcinile vor fi procesate in ordinea “oraselor” si nu in ordinea disponibilitatii rezultatelor (sa presupunem ca obtinerea datelor legate de vreme pentru primul oras ia de trei ori mai mult decat obtinerea celor pentru alte orase, caz in care vom astepta ca sistemul sa termine procesarea rezultatelor pentru primul oras, chiar daca rezultatele pentru celelalte doua sunt deja disponibile).

Solutia este sa folosim idiomul Process Tasks by Completion (ii putem spune si pattern daca vreti) care lucreaza astfel: sarcinile ar trebui abordate nu in ordinea lansarii ci in ordinea completarii lor.
Iata cum v-a arata:

Completion_Idiom_2.jpg

NOTA

In anumite cursuri acest idiom nu este numit “Process Tasks One by One” ci IMHO – este neclar si pentru mine ce inseamna. Asa ca daca considerati ca una din cele doua denumiri este mai buna la a transmite esenta puteti sa o folositi pe aceasta!

Metoda ManualProcessByCompletion incepe sa adune date legate de vreme prin GetWeatherForAsync pentru toate orasele, ambaland un obiect de tip anonim intr-o sarcina (mai tarziu va voi explica de ce acest lucru este necesar). Apoi, in cadrul buclei while apelam Task.WhenAny si primim prima sarcina terminata.

Astfel sarcinile sunt procesate in ordinea completarii, nu in ordinea lansarii.

Dar in acest caz, sarcina contine raspunsul la intrebarea “Cum este vremea in acest oras?” dar nu include intrebarea in sine (numele orasului). Trebuie sa gasim o modalitate prin care sa combinam rezultatele cu contexul executarii. Pentru a face asta utilizam metoda TaskEx.FromTask:

Completion_Idiom_3.jpg

Metoda TaskEx.FromResult creaza o sarcina proxy care se va termina la finalizarea sarcinii originale. Un taskSelector delegate iti permite sa “extragi” sarcina din obiectul principal. Acest lucru iti permite sa utilizezi in mod convenabil aceasta abordare cu tipuri anonime.

Noua abordare functioneaza mai bine fata de cea originala dar este un pic mai dificila. Ca atare este nevoie de un wrapper care sa ne permite sa o reutilizam.

Completion_Idiom_4.jpg

Codul complet pentru clasa este disponibil pe GitHub, dar iata ce inseamna. Metoda converteste o task sequence intr-o alta task sequence, ordinea acestora fiind determinata de ordinea completarii taskurilor si nu ordinea secventei originale.

Si iata cum arata exemplul:

Completion_Idiom_6.jpg

Suntem inapoi la versiunea originala in termeni de sintaxa, dar apar si comportamente noi. Iata rezultatul executiei care arata ca toate sarcinile sunt lansate simultan si procesarea are loc pe masura ce rezultatele devin disponibile:
[12:54:35 PM]: Obtinem vremea pentru 'Moscow'
[12:54:35 PM]: Obtinem vremea pentru 'Seattle'
[12:54:35 PM]: Obtinem vremea pentru 'New York'
[12:54:36 PM]: Procesarea datelor legate de vreme pentru 'Seattle': 'Temp: 7C'
Am obtinut starea vremii pentru 'Moscow'
[12:54:39 PM]: Procesarea datelor legate de vreme pentru 'Moscow': 'Temp: 6C'
Am obtinut starea vremii pentru 'New York'
[12:54:40 PM]: Procesarea datelor legate de vreme pentru 'New York': 'Temp: 8C'

UPDATE:

Eliminarea Query Comprehension Syntax iti va permite sa simplifici ultimul exemplu si mai mult:

Completion_Idiom_6.jpg

Si optiunea propusa de @hazzik bazata pe Rx-es:

Completion_Idiom_7.jpg

Functioneaza in felul urmator: mai intai luam o secventa de taskuri, le transformam intr-o secventa de Ienumerable(iobservable>. Care este mai apoi este imbinata intr-o secventa Iobservable.

Trasatura specifica unei asemenea abordari este ca in acest caz apelarea MoveNext intr-o bucla foreach este blocata.

P.S.: Codul poate fi gasit la https://github.com/SergeyTeplyakov/TplTipsAndTricks

Sergey Teplyakov
Expert in .Net, С++ and Application Architecture

Daca iti place acest articol, distribuie-l si prietenilor tai!




Mai ai intrebari?
Contacteaza-ne.
Thank you.
Your request has been received.