\


 Friday, 26 February 2021
Knihovna RStein.AsyncCpp (Task Parallel Library for C++) dospěla do verze 0.0.0.7 a najdete ji ve vcpkg katalogu

Knihovna RStein.AsyncCpp (Task Parallel Library for C++) je ve verzi 0.0.7 a dá se snadno nainstalovat pomocí Microsoft vcpkg.

vcpkg install rsasynccpp rsasynccpp:x64-Windows

(Včera mi Microsoft udělal radost a mergnul PR do masteru - https://github.com/microsoft/vcpkg/pull/16380)

Detaily k instalaci různých verzí knihovny jsou zde.

https://github.com/renestein/Rstein.AsyncCpp#Build-Library

Jestli někdo používáte UWP, tak vás potěší, že triplet UWP ve vcpkg je také podporován a všechny testy prošly.

Jestli nechcete používat vcpkg, můžete buildovat z příkazové řádky a pořád samozřejmě také i z Visual Studia 2019.

Dále knihovna podporuje coroutines ze standardu C++ 20 a stále podporuje i "legacy" coroutines.(https://devblogs.microsoft.com/.../c-coroutines-in.../)

Kromě kompilátoru MSVC cl knihovna nyní podporuje i Clang.

Třešničkou je, že na konci aplikace už není třeba volat metodu Scheduler::StopDefaultScheduler().

Parsování celého Shakespearova díla a vypsání 50 nejfrekventovanějších slov za cca 1,2 s. (Clang a (neoptimalizovaní) map/reduce aktoři na obyčejném Lenovo ThinkPadu z roku 2016).
clang_Shakespeare_Top_Words

v0.0.7

  • Added new solution configurations with static CRT (/MT, MTd). (primary consumer of these configurations is vcpkg manager)


v0.0.6



Friday, 26 February 2021 10:12:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C++


 Wednesday, 02 September 2020
RStein.AsyncCpp v. 0.5.0.0 (20200901) – threadless actors

https://github.com/renestein/Rstein.AsyncCpp

PR, stars, opinions, issues are more than welcome! Thanks!

Added threadless actors:

Planned features:

  1. Dynamic proxy for simplified creation of actors from classes with an interface that contains only void returning or/and Task<T> returning methods. AFAIK C++ does not have support for the "dynamic" proxy. "Classic" smart proxy may be used only for pre-processing and post-processing of the method call in the "real" subject.

  2. Asynchronous logic (support for co_await/co_return) in "OOP style" actors.

  3. The context for messages (a reference to a sender, a reply address, etc.)

  4. FSM actors.

  5. "Supervisors".

  6. More samples.

* What is an actor? https://en.wikipedia.org/wiki/Actor_model

Wednesday, 02 September 2020 11:18:57 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C++


 Wednesday, 03 June 2020
RStein.AsyncCpp (06-03-2020) - C++ Task Parallel Library

https://github.com/renestein/Rstein.AsyncCpp

Changes:

- Added AsyncMutex synchronization primitive.

https://github.com/renestein/Rstein.AsyncCpp#AsyncMutex

- TaskFactory.Run automatically unwraps nested Task (e.g. scheduled lambda-coroutine returns Task). Very convenient and prevents some hard-to-debug, but easy to introduce bugs in C++ coroutines.

https://github.com/renestein/Rstein.AsyncCpp#TaskFactory-Unwrap-Nested-Task

- Task has the Unwrap method ((Unwrap Task<Task<T> and returns Task<T>) and corresponding Fjoin method.

https://github.com/renestein/Rstein.AsyncCpp#Task-Fjoin

- Added SynchronizationContext - provides a mechanism to queue work to a specialized context. (useful for marshaling calls to UI thread, event loop, etc.)

https://github.com/renestein/Rstein.AsyncCpp#synchronizationcontext

- Added SynchronizationContextScope - RAII class for SynchronizationContext. An instance of this class captures the current synchronization context in the constructor (now 'old' context), installs new synchronization context provided by the user, and restores 'old' synchronization context in the destructor.)

https://github.com/renestein/Rstein.AsyncCpp#SynchronizationContextScope

- Added ConfigureAwait method (for the Task awaiter) - configures if the 'co_await continuation' is resumed in the specific synchronization context.

https://github.com/renestein/Rstein.AsyncCpp#ConfigureAwait

- Added GlobalTaskSettings::UseOnlyConfigureAwaitFalseBehavior configuration key - set the key to true if you want to enforce the equivalent of the 'co_await someTask.ConfigureAwait(false)' for all 'co_await someTask{anything}' expressions in the application - synchronization context is then never used when resuming the 'co_await continuation'.

https://github.com/renestein/Rstein.AsyncCpp#GlobalTaskSettings-UseOnlyConfigureAwaitFalseBehavior

- Task.ContinueWith method has new overloads with CancellationToken argument.



Wednesday, 03 June 2020 10:43:27 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C++ | Nativní kód


 Monday, 04 May 2020
C++ Task Parallel Library (TPL)

Příspěvek nejen pro ty, kteří se od března do června nedočkají kvůli COVIDu C++ kurzu. Kdyby se někdo nudil, nebo mu lezly na mozek ty neustále se opakující zprávy o Babišovi, rouškách a další variace na mem "všichni tady chcípnem, když ne na COVID, tak na sucho", dovolím si upozornit na jednu poměrně čerstvou (neoptimalizovaná alfa) asynchronní švestičku ze své zahrádky. Švestičky i optimisticky naznačují roční období, kdy se snad uvidíme, jestliže macecha příroda nereleasne implementaci zmutované specifikace COVID++.

RStein.AsyncCpp používající coroutine z C++ 20 je knihovna, ve které se rychle zorientuje každý, kdo zná Task Parallel Library (.NET, C#).

V knihovně najdete nejen:

Task (tedy něco jako std:future) - tásky jsou narozdíl od knihovny cppcoro, kterou asi znáte, 'hot' - tedy přes TaskFactory je Task rovnou nastartován a naschedulován k vyřízení.
Task má API, které čekáte. A narozdíl od std::future má metodu ContinueWith (then).

Task se dá "co_awaitovat", protože podporuje concept "Awaiter". A můžete ho samozřejmě použít jako návratovou hodnotu z coroutine metody ("coroutine promise type").

Dále jsou v knihovně jednoduché metody pro vytvoření dokončeného Tasku z předpřipravené hodnoty (TaskFromResult), z výjimky (TaskFromException), nebo lze vrátit Task ve stavu Canceled (TaskFromCanceled).

Jednoduché DataFlow. ("flat", "fork-join" a a další typy).

Kombinátory pro Task:
WaitAll.
WaitAny.

TaskCompletionSouce - std:: promise bez těch otravných věcí, které určitě znáte sami.

Funkcionální kompozice tasku.
Fbind (alias bind, SelectMany, flatMap, mapMany)
Fmap (map, Select)

Asynchronní primitivy.
AsyncSemaphore.

Kooperativní storno pomocí tříd CancellationTokenSource a a CancellationToken.

AsyncProducerConsumerCollection.

Více zde:
https://github.com/renestein/Rstein.AsyncCpp
-------------------------------------------------------------------------------

Dear friends/followers,
maybe the result of my experimentation with coroutines may be interesting for someone else.
The RStein.AsyncCpp library (early unoptimized alpha) is a set of types that should be familiar for anyone who knows the Task Parallel Library (TPL) for .NET (C#). In addition, this library contains simple DataFlow, functional combinators for the Task class, useful async primitives (AsyncSemaphore, AsyncProducerConsumerCollection, CancellationToken, CancellationTokenSource ...).
The library is my playground for testing coroutine support in C++.
The library supports compilation in the VS 2019. Support for other compilers is planned.
More info.
https://github.com/renestein/Rstein.AsyncCpp



Monday, 04 May 2020 09:24:33 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C++ | Nativní kód


 Wednesday, 13 February 2019
ConfigureAwaitEnforcer – extenze pro Visual Studio

I když je tento blog nechutná a technicky zaostalá zombie, která vede k úvahám, jak konečně tuhle bestii zabít, aby mě už nestrašila, dá se přesto, nebo možná právě proto :), využít k šíření ConfigureAwait infekce v cizím kódu.  I tady by snad ještě někoho mohla zajímat moje extenze pro Visual Studio, která zkontroluje:

1) Jestli jste při použití ‘await someTask’ nezapomněli na ConfigureAwait(false).

Když jste tuhle chybu udělali, extenze dotyčný řádek jako prototypická labilní nervní učitelka červeně podtrhne a:

a) Nabídne úpravu výrazu přidáním ConfigureAwait(false).

b) Nabídne úpravu výrazu přidáním ConfigureAwait(true).

Nejlepší je extenzi vidět v akci.
yDitj9JOJh

Ve verzi 1.1 si můžete zvolit i závažnost diagnostiky (Error, Warning, Info, Hidden).

Analyzér je dostupný i na nugetu.
https://www.nuget.org/packages/ConfigureAwaitEnforcer/

Zdrojové kódy.

Bitbucket
https://bitbucket.org/renestein/configureawaitenforcer/src/master/

Github

https://github.com/renestein/ConfigureAwaitEnforcer



Wednesday, 13 February 2019 12:45:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C#


 Friday, 03 October 2014
Materiály z přednášky TPL - konkurenční, paralelní a asynchronní kód pro náročné pro WUG Praha

Prezentace:

Ad hoc příklady

https://bitbucket.org/renestein/wugtplex.ref.p/src

Knihovna Rstein.Async

https://bitbucket.org/renestein/rstein.async/src

Seriál o knihovně Rstein.Async

http://jdem.cz/ba8kp3

Cesta k příkladům s aktory v knihovně Rstein.Async

https://bitbucket.org/renestein/rstein.async/src/93fe127f35ac3b37d7ff31aa2a25d6a80fc1ce0b/RStein.Async.Examples/?at=master



Friday, 03 October 2014 11:23:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  


 Tuesday, 19 August 2014
Veřejná přednáška pro WUG - TPL – konkurenční, paralelní a asynchronní kód pro náročné.

Rád bych vás pozval na svou přednášku, kterou pořádá WUG.

Název přednášky: TPL - konkurenční, paralelní a asynchronní kód pro náročné.

Datum konání: 2.10.2014 od 17:30 do 21:00

Místo konání: pobočka: BB centrum, budova Alfa (Aquarius), Vyskočilova 1461/2a, Praha 4

Registrace na přednášku: http://wug.cz/praha/akce/597-TPL-konkurencni-paralelni-a-asynchronni-kod-pro-narocne

Anotace přednášky:

Znáte alespoň trochu Task Parallel Library a přednášek slibujících další nenáročný „úvod do TPL“ jste už viděli dost? Myslíte si, že klíčová slova async/await v C# jsou magií kompilátoru, jejíž kouzlo pro vás už navěky pominulo po zhlédnutí triviálních a donekonečna opisovaných příkladů, jak zavolat asynchronně pár nudných webových služeb?

Na přednášce probereme, jak rozšířit knihovnu TPL o další užitečné konstrukce i jak odstranit některá omezení v současné verzi TPL. Podíváme se na různé způsoby psaní konkurenčního, paralelního a asynchronního kódu. U konkurenčního kódu se zaměříme (nejen) na aktory a porovnáme různé způsoby, jak můžeme aktory psát.

Nezapomeňte s sebou vzít i kolegy, kteří hlásají, že každou nebezpečnou hlavu konkurenčního kódu setne jeden pořádný „lock“, a to nejlépe rekurzivní, aby vás deadlock nebo livelock ve firmě zabavil i o dlouhých zimních večerech.



Tuesday, 19 August 2014 11:08:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | C# | Návrhové vzory


 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


 Monday, 16 June 2014
Task Parallel Library a RStein. Async 4 z n – ThreadPoolScheduler založený na IoServiceScheduleru.

(Starší verze obnovena ze zálohy 21. 1. 2020.)


V předchozím díle o coroutines jsme poprvé viděli, jak se dá použít IoServiceScheduler. V tomto článku uvidíme, že pár metod IoServiceScheduleru stačí i k napsání jednoduchého threadpoolu. Tento článek i následující článek jsou oproti předchozím článkům kratší  a oddechové, abychom získali důvěrný vztah ke způsobu práce se schedulery v knihovně  RStein.Async, a nepřekvapil nás v šestém díle StrandScheduler, se kterým se vydáme mezi aktory.

Knihovna RStein.Async 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ší.

ThreadPool z .Net Frameworku nebo threadpool z WIN32 API znáte. ThreadPool není v základu nic jiného než volné sdružení déle žijících threadů, které bylo založeno za účelem rychlého zpracování tásků. ThreadPool se používá hlavně proto, abychom v aplikace nevytvářeli a nelikvidovali thready, jak se nám zlíbí, protože thready nejsou zrovna “laciné” objekty na vytvoření, správu ani likvidaci, Jak jsem již v seriálu zmínil, TaskScheduler.Default využívá k vyřizování tásků standardní .Net ThreadPool.

Náš threadpool bude oproti třídě ThreadPool v .Net Frameworku velmi jednoduchý. Hlavním rozdílem bude to, že nebudeme podporovat “work stealing queue”, i když by nebyl příliš velký problém takovou podporu dopsat.

Dalším podstatným rozdílem oproti threadpoolu v .Net frameworku  je to, že náš threadpool bude k vyřizování tásků od svého vytvoření až do svého zrušení  používat fixní a po celou dobu svého života stejný počet threadů. V .Net threadpoolu se může počet threadů měnit. .Net threadpool přidá thread mj. v situaci, kdy předpokládá, že mohlo dojít k deadlocku.

Kdy může v threadpoolu dojít k deadlocku?

  1. Představme si, že v threadpoolu je 10 threadů.
  2. Všech 10 threadů je používáno a vyřizuje nějaké tásky. 9 threadů v threadpoolu čeká na dokončení tásku v threadu  s číslem 10.
  3. Tásk v threadu 10 zařadí ke zpracování v threadpoolu další tásk (tásk 11) a čeká na jeho dokončení.
  4. Všech 10 threadů je vytíženo, tásk s pořadovým číslem 11 nebyl ještě spuštěn. Máme klasický příklad deadlocku, protože na dokončení tásku 11 čeká tásk v threadu 10 a na dokončení tásku v threadu 10 čekají tásky v threadech 1-9.  Snad jen Coffman by měl z takové situace radost.

Heuristika .Net threadpoolu po nějakém čase iniciuje vytvoření další threadu, protože je zřejmé, že všechny thready v threadpoolu jsou využity, ale žádné tásky nebyly již “delší dobu” dokončeny. Nově vytvořený thread vyřídí tásk s číslem 11, tím se uvolní tásk v threadu 10 a doběhnou i tásky v threadech 1-9. Voilà, deadlock byl odstraněn. Samozřejmě že tohle je jen jedna z variací mnoha zhoubných scénářů, které si asi všichni dokážeme představit. Tásk v threadu 11 by mohl třeba vygenerovat tásky 12-20 a čekat na jejich dokončení a threadpool by chtě nechtě přidával další a další thready.

Já tohle chování threadpoolu, které se na první pohled může zdát jako vstřícné a bezproblémové, nepovažuju za moc vhodné, protože threadpool jím kamufluje po dlouhou dobu některé nepříjemné chyby v logice aplikace a toleruje i velmi těsné a nevhodné vztahy mezi tásky. Náš threadpool nikdy po počáteční inicializaci fixního počtu threadů žádný další thread nepřidá. A to ani tehdy, jestliže je mu předán tásk, u něhož je nastaven příznak LongRunning. Jestliže to někomu vadí, pull request je vítán.

IoServiceThreadPoolScheduler staví na konstrukcích pro schedulery, které jsme si vysvětlili v prvním díle.

Image

Na IoServiceThreadPoolScheduleru je nejzajímavější, že nám k napsání threadpoolu stačí pár řádků. Většinu práce opět odvede IoServiceScheduler. Snad teď už začíná být zřejmé, proč jsem IoServiceScheduleru věnoval tak dlouhý díl.

  • IoServiceThreadPoolScheduler vyžaduje v konstruktoru instanci IoServiceScheduleru a počet threadů, které budou tvořit threadpool. Jestliže počet threadů neurčíme, vytvoří se počet threadů shodný s počtem dostupných logických procesorů. 
  • V privátní metodě initThreads volané z konstruktoru thready vytvoříme a ihned je propůjčíme IoServiceScheduleru tím, že zavoláme jeho metodu Run. Každému threadu také přidělíme jméno a nastavíme u něj příznak IsBackground na true, protože thready v threadpoolu by neměly blokovat ukončení procesu.
    Již v konstruktoru IoServiceThreadPoolScheduleru jsme vytvořili objekt Work, o kterém víme, že zajistí, aby metoda Run nevrátila řízení, ani když žádné tásky neexistují a thread tedy neskončí. Musíme si poradit jen s tím, že při vyřizování některého tásku dojde k výjimce. Jestliže aplikace běží pod debuggerem, díky metodě Debugger.Break může autor aplikace začal pátrat po příčině kritické chyby, jinak odstřelíme aplikaci voláním metody Environment.FailFast. Mrtvý proces je kupodivu lepší proces než proces s narušenými daty a/nebo vyšinutou logikou zpracování.
  • V metodě Dispose nejprve zrušíme objekt Work a počkáme, až všechny thready v threadpoolu vyřídí všechny zbývající tásky a skončí svou činnost. IoServiceThreadPoolScheduler považuju instanci IoServiceScheduleru za instanci, kterou exkluzivně vlastní, a proto i na ní zavolá metodu Dispose.
  • Metody QueueTask, TryExecuteTaskInline a GetScheduledTasks, které musí mít každý scheduler v knihovně RStein.Async, jen delegují na stejně nazvané metody IoServiceScheduleru. IoServiceThreadPoolScheduler, který bude z hlediska aplikace viditelným schedulerem pro zpracování tásků, také zajistí, že předaný ProxyScheduler bude používat i podkladový a pro aplikaci jinak neviditelný IoServiceScheduler. Opět podotknu, že kdyby někdo tápal, proč používám ProxyScheduler, v prvním  díle seriálu jsou “proxy” schedulery i “reálné” schedulery podrobně popsány včetně důvodů pro zavedení této distinkce mezi schedulery.
  • Možná někoho z vás zarazilo, že náš threadpool nemá žádnou metodu QueueUserWorkItem, kterou asi znáte z .Net threadpoolu. Napsání takové statické metody je jednoduché, ale různé instance ThreadPoolScheduleru mohou být využívány jako samostatné a navzájem na sobě nezávislé TPL schedulery v různých částech aplikace a neomezují nás tím, že bychom měli v jedné aplikační doméně jen jeden threadpool, do kterého statická metoda QueueUserWorkItem beze všech skrupulí hází všechny tásky.

Příště se podíváme na testy, ze kterých vyplyne, jak má být ThreadPoolScheduler používán, a proč metodu QueueUserWorkItem nepotřebujeme.



Monday, 16 June 2014 06:06:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C#


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

(Starší verze obnovena ze zálohy 21. 1. 2020.)


V předchozím dílu seriálu o TPL a knihovně RStein.Async a knihovně jsme napsali IoServiceScheduler.

Dnes se podíváme,  jak se dají s IoServiceSchedulerem napsat tzv. “coroutines” .

Knihovna RStein.Async 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ší.

Nejdříve krátké vysvětlení, co jsou “coroutines”.

Metody/funkce určitě dobře znáte, a proto víte, že do metody vstoupíte, metoda udělá svou práci a pak skončí. Metoda nemusí skončit úspěšně, může dojít k vyvolání výjimky, v průběhu může metoda volat další metody, ale klíčové je, že metodu aktivujeme, metoda udělá svou práci a skončí. Spustíme metodu, metoda udělá svou práci a skončí. Start a pak Stop. Když metodu zavoláme znovu, metoda se provede vždy od prvního řádku.

Coroutines jsou zajímavější stvoření. Nejlépe si je můžeme představit jako metody, které se dají dočasně pozastavit, a po chvíli je můžeme znovu spustit od místa, kde jsme je pozastavili. A pak je můžeme pozastavit znovu. Jako když na dálkovém ovladači  pozastavíte rozkoukaný film, odběhnete na WC, pustíte film znovu, pak pozastavíte film, abyste odpověděli manželce/přítelkyni/dětem/psovi na nějaký záludný dotaz/skřek/štěk, spustíte film a užijete si závěrečné titulky. A stejně jako se při pozastavení filmu uloží místo, kde jste skončili, abyste se nemuseli dívat po každé pauze na film od začátku, u coroutine uložíme  stav metody (hodnoty proměnných), abychom po krátké “pauze” mohli bez problémů pokračovat na dalších řádcích metody.

Coroutine:

Start metody, Pauza a uložení stavu, Start na uložené pozici  a obnovení stavu, Pauza a uložení stavu, Start na uložené pozici a obnovení stavu, Pauza a uložení stavu, […], Stop metody.

“Klasická” Metoda:

Start metody, Stop metody.

Z předcházejících řádků by mělo vyplynout, proč se o klasických metodách/funkcích (“subroutine”) občas mluví jako o speciálních nebo degenerovaných případech coroutine. Subroutine se od coroutine liší tím, že mezi spuštěním a ukončením  metody není žádná pauza.

K čemu se dají coroutines využít?

”Coroutines” se dají mj. považovat za lehkotonážní náhradu threadů. I když by asi bylo lepší napsat, že coroutines jsou odlehčeným doplňkem threadů než jejich náhradou. Thready jako základní jednotku konkurence v operačním systému Windows můžeme při použití TPL sice ignorovat, ale to neznamená, že neexistuje. A znáte to – kdo nezná svou historii, je nucen ji prožít znovu a zopakovat si s TPL tásky chyby, které jsou známy už z naivní práce s thready. Veselý obličej

Pomocí coroutines můžete v aplikaci podporovat kooperativní multitasking. Kooperativní proto, že každá coroutine se v nějakém okamžiku sama vzdá řízení (bude “zapauzována”) a předá řízení své kolegyni. Thready podporují preemptivní multitasking – zjednodušeně řečeno, o jejich spuštění, pozastavení  a času, který je threadu poskytnut,  rozhoduje operační systém.

Lehkotonážní jsou coroutines proto, že na jeden thread v operačním systému můžeme namapovat libovolné množství “coroutines”. Adjektivum lehkotonážní u coroutines  vyjadřuje také to, že při kooperativím přepnutí řízení z jedné coroutine do druhé se vyhneme relativně drahému  “context switchi” a opakovanému přechodu z “user” módu do ”kernel” módu, ke kterému může dojít, když koordinujeme postup threadů v aplikaci pomocí primitiv Semaphor, ManualResetEvent(Slim), .AutorResetEvent atp.

Coroutines mohou být zajímavé i proto, že když dovolíme, aby v nějaké množině coroutines běžela v každém časovém okamžiku maximálně jedna coroutine, pak si můžeme být jisti, že i když tisíc různých coroutines modifikuje sdílený stav, který není v kritické sekci a ani k němu není řízen přístup pomocí jiné synchronizační primitivy, tak se nic neděje a ke ke korupci dat nemůže dojít, protože se stavem nikdy nepracujeme současně z více threadů/coroutines.

V tomto článku si napíšeme podporu pro jednoduché coroutines, které budou na sobě vzájemně nezávislé, nebudou si předávat žádná data (nepůjde tedy o coroutines s podporou pull/push hodnot) a jen zajistíme, že poté, co bude jedna coroutine “zapauzována”, tak se ihned spustí následující coroutine.

Od C# 2.0 bylo možné psát “coroutines” pomocí kontextového slova yield. I když jste možná yield k psaní coroutine nikdy nepoužili, můžete se na pěkný příklad takových coroutines podívat v MVVM framoworku Caliburn.Micro.

Po přidání klíčových slov async a await do C#, je vytváření coroutine mnohem snazší, protože veškerou těžkou práci odvede kompilátor.

Kdykoli v async metodě použijeme klíčové slovo await, tak kompilátor zaručí, že poté, co awaiter tvrdí, že tásk ještě není dokončen, tak je metoda pozastavena, je uložen současný stav metody a metoda je “roztržena” na dvě části. Ještě neprovedený kód metody a současný stav metody je uložen do “continuation” delegáta, který je vyvolán  “později”, když je tásk dokončen. V delegátu “continuation” je kód, který musí být proveden po “odpauzování” metody. Mým cílem není teď v článku vysvětlovat všechny nuance klíčových slov async, await ani awaiterů, to bychom popsali hodně listů na blogu, ale jen zrekapituovat a akcentovat, že kompilátor je schopen pozastavit provádění metody a požádat awaiter, aby někdy později zbývající kód v metodě spustil.

Metoda spuštěna – metoda “zapauzována” (magie kompilátoru), je uložen stav metody (delegát continuation) a continuation je předána awaiteru – awaiter “odpauzuje” metodu (vyvolá  continuation) - metoda “zapauzována” (magie kompilátoru), je uložen stav metody (delegát continuation) a continuation je předána awaiteru – awaiter “odpauzuje” metodu (vyvolá  continuation) – […]  - Metoda ukončena.

Tento rytmus zpracování async metody se shoduje s rytmem životního cyklu coroutine.

Start metody, Pauza a uložení stavu, Start na uložené pozici  a obnovení stavu, Pauza a uložení stavu, Start na uložené pozici a obnovení stavu, Pauza a uložení stavu, […], Stop metody.

Na nás je jen zaručit, že coroutines podporují kooperativní multitasking.

Když bude chtít jedna metoda předat řízení jiné metodě, stačí, aby zavolala await na objektu coroutine, který metodám předáme jako argument.

await coroutine; //=vzdávám se řízení, přišel čas předat vládu jiné metodě.

Napíšeme si tedy třídu Coroutine, která se spolehne na služby IoServiceScheduleru  a která má roli awaiteru.

Každý awaiter by měl podporovat rozhraní INotifyCompletion, a proto tak činí i naše třída Coroutine.

Coroutine předáme odkaz na IoServiceScheduler a IoServiceScheduler také zastane veškerou práci.

Náš awaiter z metody IsCompleted vrací false, protože potřebujeme, aby každá metoda, která zavolá await, byla zapauzována – vrácení hodnoty false je příkazem, aby byl vygenerován delegát continuation. Awaiter dostane objekt continuation jako argument metody onCompleted.  Jediné, co uděláme, je, že delegáta předáme do metody Post IoServiceScheduleru. Připomeňme, že metoda Post  v IoServiceScheduleru vytvoří z předaného delegáta tásk a tento tásk je zařazen ke zpracování na konec fronty, takže dáme šanci pokročit ve zpracování i dalším metodám.

Metoda Run u třídy Coroutine jen deleguje na metodu Run IoServiceScheduleru. v propůjčeném se budou střídat vykonání jednotlivých metod-coroutines.


Co nám ještě chybí? Metody, na kterých si ukážeme, jak si coroutines předávají řízení a také kód, který vytvoří IoServiceScheduler pro koordinující objekt Coroutine (awaiter) a dovolí jednotlivé metody-coroutines spustit.

Metoda-konkrétní coroutine.

Metoda Start dostane odkaz na objekt Coroutine a vypíše nám informace, kde se při svém zpracování nachází. My můžeme argumentem numberOfIterations předaným do konstruktoru ovlivnit počet iterací metody.

Všimněte si také, že await volám nejen na argumentu coroutine, ale i na TPL metodách Task.Yield a Task.Delay. I u nich by mělo platit, že se metoda vzdá dočasně řízení, spustí se jiná coroutine a po nějaké době bude metoda znovu spuštěna, aniž by její běh interferoval s během jiných metod–coroutines. U nás tomu tak skutečně bude, ale spíš jde o šťastnou souhru okolností.

Jestliže zavoláte await, objekt continuation je spuštěn v zachyceném synchronizačním kontextu, anebo, když synchronizační kontext neexistuje, tak se použije současný scheduler, a když ani ten neexistuje, tak se použije TaskScheduler.Default. My spustíme všechny metody přes IoServiceScheduler, který používá i třída Coroutine – protože v konzolové aplikaci synchronizační kontext není, použije se dostupný scheduler, a to je náš IoServiceScheduler. Všechny objekty “continuation” tak nakonec skutečně budou spuštěny v threadu IoServiceScheduleru. Abychom nebyli v podobných situacích závislí na nahodilé souhře okolností, napíšeme si v jedenáctém díle seriálu nový synchronizační kontext – IoServiceSynchronizationContext.

Zbývá dopsat spuštění coroutines.

  1. V konstruktoru vytvoříme scheduler a předáme ho instanci třídy Coroutine.
  2. V metodě Start přidáme coroutine voláním metody addCoroutines, ve které si můžete pohrát s nastavením, kolik coroutines vznikne (konstanta NUMBER_OF_COROUTINES) a kolik iterací cyklu (konstanta NUMBER_OF_ITERATIONS), který jsme viděli výše,  provede  každá coroutine. Coroutines připravíme ke spuštění přes metodu Post IoServiceScheduleru. Do IoServiceScheduleru přidáme také tásk, který se spustí v momentě, kdy všechny coroutines doběhnou, a tento tásk odstraní (Dispose) objekt Work, o kterém víme, že udrží metodu Run IoServiceScheduleru v chodu, i když zrovna v IoServiceScheduleru nejsou ve frontě žádné tásky. Objekt Work potřebujeme, protože naše coroutine používají metodu Task.Delay, takže by se mohlo stát, že IoServiceScheduler nezpracovává žádné tásky-continuation, protože všechny coroutines čekají na dokončení metody Delay.
  3. Metoda Start zavolá Run na IoServiceScheduleru. První coroutine se díky IoServiceScheduleru rozeběhne.

Spuštění našich testovacích coroutines:

Je vidět, jak se jednotlivé coroutines střídají při zpracování a jak si také předávají právo použít jeden jediný thread (tid ve výpise), který jsme propůčili IoServiceScheduleru. 

Image

Coroutines jsou tedy z určitého úhlu pohledu logické thready, které nyní mapujeme na jeden fyzický thread.

V tomto příspěvku jsem chtěl, abychom viděli, jak málo stačí k napsání coroutine v moderním C#, který je doplněn službami IoServiceScheduleru. Bylo by snadné rozšířit příklad o coroutines, které si nejen předávají řízení, ale které si i vyměňují informace. Pro vážné zájemce o coroutines také doplním, že na OS Windows jsou dostupné objekty Fiber. Přesto skepticky dodám, že jsem na moderním HW nenašel moc důvodů, proč objekty Fiber používat, a myslím si, že význam měly hlavně v době, kdy “context switch” byl u staršího HW nejen relativně, ale po změření celkové časové náročností operace i absolutně velmi drahý.

V příští části nás čeká ThreadPoolScheduler.



Monday, 09 June 2014 05:32:00 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  C#