\


 Wednesday, 25 June 2014
Task Parallel Library a RStein. Async 5 z n – Hrajeme si s ThreadPoolSchedulerem


Po napsání ThreadPoolScheduleru v předchozím díle následuje další slíbený oddychový díl, ve kterém si máme s ThreadPoolSchedulerem pohrát. Název je možná trochu zavádějící, protože nás žádné rozkošné hrátky nečekají.ThreadPoolScheduler  zcela pragmaticky otestujeme, abychom si potvrdili, že jde o plně funkční threadpool  a že se takový threadpool dá použít všude, ke je očekáván TPL scheduler.

Jako vždy připomenu, že knihovna RStein.Async, ve které naleznete i ThreadPoolScheduler, je dostupná  na Bitbucketu.
git clone git@bitbucket.org:renestein/rstein.async.git


Seriál  Task Parallel Library a RStein.Async  (předběžná osnova)

Task Parallel Library a RStein. Async 1 z n –  Popis základních tříd a obcházení omezení v TPL.

Task Parallel Library a RStein. Async 2 z n –  (boost) ASIO v .Net a IoServiceScheduler.

Task Parallel Library a RStein. Async 3 z n – Ukázky použití IoServiceScheduleru. Coroutines.

Task Parallel Library a RStein. Async 4 z n  – ThreadPoolScheduler založený na IoServiceScheduleru.

Task Parallel Library a RStein. Async 5 z n – Hrajeme si s ThreadPoolSchedulerem.

Task Parallel Library a RStein. Async 6 z n – Vytvoření StrandScheduleru.

Task Parallel Library a RStein. Async 7 z n – Náhrada za některé synchronizační promitivy – ConcurrentStrandSchedulerPair.

Task Parallel Library a RStein. Async 8 z n – Jednoduchý “threadless” actor model s využitím StrandScheduleru.

Task Parallel Library a RStein. Async 9 z n – Píšeme aktory I.

Task Parallel Library a RStein. Async 10 z n – Píšeme aktory II.

Task Parallel Library a RStein. Async 11 z n – Píšeme nový synchronizační kontext  - IoServiceSynchronizationContext.

Task Parallel Library a RStein. Async 12 z n – Použití IoServiceSynchronizationContextu v konzolové aplikaci a Windows službě.

(bude upřesněno)


Poznámka: V celé sérii článků budu používat slovo Task pro třídu, task pro název proměnné / argumentu metody a ”anglicismy” tásk/tásky místo “úloha/úlohy“ nebo jiného českého patvaru při zmínce o /úlohách-táscích/ v dalším textu. Předpokládám, že pro většinu vývojářů je takový text srozumitelnější.

Vytvoříme si ThreadPoolScheduler pro testy.

V metodě InitializeTest třídy IoServiceThreadPoolSchedulerTests vytvoříme IoServiceScheduler, předáme ho do konstruktoru ThreadPoolScheduleru a ThreadPoolScheduler poslouží k inicializaci  ProxyScheduleru.

ProxyScheduler našeho ThreadPoolScheduleru je TPL scheduler pro TaskFactory. Tásky vytvořené v testTaskFactory zpracuje ThreadPoolScheduler.

m_testTaskFactory = new TaskFactory(ProxyScheduler.AsTplScheduler());

Vše o vzájemných vztazích mezi “proxy” schedulery a “reálných” schedulery naleznete v prvním díle seriálu.

Přišla také chvíle, abych vysvětlil, proč existuje třída IAutonomousSchedulerTests a upřesnil terminologii v knihovně RStein.Async. Ve třídě IAutonomousSchedulerTests se nacházejí testy, kterými musí projít všechny autonomní schedulery. Jako autonomní scheduler označuju takový scheduler, který po svém vytvoření ihned zpracovává předané tásky a nevyžaduje ze strany aplikace již žádnou další konfiguraci ani podporu při zpracování tásků. Z schedulerů, které prozatím známe, můžeme za autonomní schedulery označit právě ThreadPoolScheduler nebo CurrentThreadScheduler, který jsme viděli v prvním díle seriálu. Naopak IoServiceScheduler není autonomní scheduler, protože po svém vytvoření čeká na to, až mu aplikace propůjčí pro vyřizování tásků thread tím, že zavolá jednu z jeho metod Run, RunOne, Poll nebo PollOne.

Nejprve otestujeme konstruktory ThreadPoolScheduleru.

Jestliže není předán IoServiceScheduler, musí být vyvolána výjimka.

Počet threadů v threadpoolu nesmí být nulový a ani neumíme vytvořit záporný počet threadů.

Jak jsem před chvílí vysvětlil, ThreadPoolScheduler je jedním z autonomích schedulerů,  a proto musí projít všemi testy pro autonomní schedulery. Většinu testů jsme viděli při testování CurrentThreadScheduleru, a proto  u následujících testů jen jen shrnu, že v jednom testu ověřujeme bezproblémové vyřízení jednoho tásku a v dalším testu zpracování většího množství tásků.

Následující test je zajímavější, protože ověřuje, že když zavoláme metodu Dispose, tak ThreadPoolScheduler vyřídí všechny zbývající tásky a teprvé poté metoda Dispose ukončí činnost scheduleru. O metodě Dispose u schedulerů chci ještě v nějakém dalším díle napsat více, protože deterministické ukončení činnosti různých schedulerů, kdy každý může mít zcela odlišné chování, je jeden z nejméně příjemných úkolů a bez ohledu na to, jaké řešení zvolíte, nezbavíte sebe ani ostatní, kteří schedulery ve svých aplikacích pouze používají, všech problémů. Jsem v čím dál silnějším pokušení některé hraniční scénáře, kdy klient nerespektuje životní cyklus schedulerů a tásků,  přesunout do temné říše nedefinovaného chování.Veselý obličej U ThreadPoolScheduleru ale nic takového nehrozí, i když byste měli mít nepříjemné mrazení z toho, že se snažíte likvidovat scheduler, aniž byste si byli jisti, že před voláním metody Dispose vyřídil všechny tásky.


Asynchronní test Dispose_When_Tasks_Are_Queued_Then_All_Tasks_Are_Executed spadá pod ty užitečné, ale ne zrovna bezpečné testy, o kterých jsem už také psal. Vygenerujeme tisíc tásků, zařadíme je ke zpracování, ale každý tásk je zablokován do té doby, dokud nestornujeme CancellationTokenSource s názvem waitForSignalCts. Za storno odpovídá metoda CancelAfter, která stornuje CancellationToken po uplynutí stanoveného času. My stornujeme CancellationToken po uplynutí jedné sekundy od zařazení tásků ke zpracování. Ihned po zavolání metody CancelAfter zavoláme metodu Dispose scheduleru a ověříme, že všechny vygenerované a scheduleru předané tásky byly zpracovány.

Kdyby to snad někomu ušlo, upozorním, že CancellationTokenSource používáme ke komunikaci mezi thready. Žádný tásk nestornujeme, jen použijeme CancellationToken k částečné synchronizaci mezi kódem v testu a kódem v táscích. Taková rychlá náhrada za synchronizační primitivu ManualResetEventSlim nebo její příbuzné.


Psal jsem, že test není bezpečný. V testu je totiž “race condition”, protože by teoreticky mohlo dojít k tomu, že metoda Dispose bude zavolána teprve poté, co jsou všechny tásky v scheduleru už vyřízeny. Takový test by opět prošel, ale ověřil  by jen chování, které už ověřuje test WithTaskFactory_When_Tasks_Are_Queued_Then_All_Tasks_Are_Executed. V této fázi vývoje knihovny RStein.Async dokážu i s takovým špinavým testem sdílet jednu “solution” ve Visual Studiu. Veselý obličej

Další testy už jsou pro všechny schedulery společné a můžete se na ně podívat sami.

Můžeme být spokojeni, všechny testy jsou zelené.

Image

V dalším díle si pořídíme zajímavý přírůstek do rodiny schedulerů s názvem StrandScheduler. Plným jménem StrandSchedulerDecorator.



Wednesday, 25 June 2014 04:30:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C# | Návrhové vzory


Comments are closed.