Vlákna vs Coroutines v Kotline

1. Úvod

V tomto rýchlom výučbe vytvoríme a vykonáme vlákna v Kotline.

Neskôr si povieme, ako sa tomu úplne vyhnúť, v prospech Kotlin Coroutines.

2. Vytváranie vlákien

Vytvorenie vlákna v Kotline je podobné ako v Java.

Mohli by sme buď predĺžiť Závit triedy (aj keď sa to neodporúča kvôli Kotlin nepodporuje viacnásobné dedičstvo):

trieda SimpleThread: Thread () {public override fun run () {println ("$ {Thread.currentThread ()} has run.")}}

Alebo môžeme implementovať Spustiteľné rozhranie:

trieda SimpleRunnable: Runnable {public override fun run () {println ("$ {Thread.currentThread ()} has run.")}}

A rovnako ako to robíme v Jave, môžeme ho spustiť volaním štart () metóda:

val thread = SimpleThread () thread.start () val threadWithRunnable = vlákno (SimpleRunnable ()) threadWithRunnable.start ()

Alternatívne, podobne ako Java 8, podporuje Kotlin konverzie SAM, preto ich môžeme využiť a odovzdať lambdu:

val thread = Vlákno {println ("$ {Thread.currentThread ()} je spustené.")} thread.start ()

2.2. Kotlin závit () Funkcia

Ďalším spôsobom je zvážiť funkciu závit () ktoré Kotlin poskytuje:

zábavné vlákno (štart: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, názov: String? = null, priorita: Int = -1, blok: () -> jednotka): vlákno

Vďaka tejto funkcii môže byť vlákno inštancované a vykonané jednoducho:

vlákno (start = true) {println ("$ {Thread.currentThread ()} je spustený.")}

Funkcia akceptuje päť parametrov:

  • štart - Ak chcete okamžite spustiť vlákno
  • isDaemon - Vytvoriť vlákno ako vlákno démona
  • contextClassLoader - Triedový nakladač, ktorý sa používa na načítanie tried a zdrojov
  • názov - Ak chcete nastaviť názov vlákna
  • prioritou - Nastaviť prioritu vlákna

3. Kotlinské rutiny

Je lákavé myslieť si, že vytvorenie ďalších vlákien nám môže pomôcť vykonať viac úloh súčasne. Bohužiaľ to nie je vždy pravda.

Vytvorenie príliš veľa vlákien môže v niektorých situáciách spôsobiť, že aplikácia bude mať nižšiu výkonnosť; vlákna sú objekty, ktoré ukladajú réžiu počas alokácie objektov a zberu odpadu.

Na prekonanie týchto problémov Kotlin predstavil nový spôsob zápisu asynchrónneho neblokujúceho kódu; Coroutine.

Podobne ako vlákna, aj korutíny môžu bežať súčasne, čakať na seba a komunikovať medzi sebou s tým rozdielom, že ich vytváranie je oveľa lacnejšie ako vlákna.

3.1. Bežný kontext

Pred predstavením staviteľov korutín, ktoré Kotlin poskytuje ihneď po vybalení, musíme diskutovať o Coroutinovom kontexte.

Coroutines sa vždy vykonávajú v nejakom kontexte, ktorý predstavuje množinu rôznych prvkov.

Hlavné prvky sú:

  • Úloha - modeluje zrušiteľný pracovný tok s viacerými stavmi a životným cyklom, ktorý vrcholí jeho dokončením
  • Dispečer - určuje, ktoré vlákno alebo vlákna používa príslušný korutín na svoju realizáciu. S dispečerom, môžeme obmedziť vykonávanie korutín na konkrétne vlákno, odoslať ho do fondu vlákien alebo nechať bežať neobmedzene

Uvidíme, ako zadať kontext, keď v ďalších fázach popíšeme korutíny.

3.2. spustenie

The spustenie function is coroutine builder that runs a new coroutine without blocking the current thread a vráti odkaz na korutín ako a Job objekt:

runBlocking {val job = launch (Dispatchers.Default) {println ("$ {Thread.currentThread ()} has run.")}}

Má dva voliteľné parametre:

  • kontext - Kontext, v ktorom sa vykonáva rutina, ak nie je definovaný, dedí kontext z CoroutineScope sa spúšťa z
  • začať - Možnosti spustenia pre coutín. V predvolenom nastavení je proces naplánovaný okamžite

Uvedomte si, že vyššie uvedený kód je spustený do zdieľaného fondu pozadia vlákien, pretože sme ich použili Dispečeri. Predvolené ktorý ho uvádza na trh GlobalScope.

Prípadne môžeme použiť GlobalScope.launch ktorý používa rovnakého dispečera:

val job = GlobalScope.launch {println ("$ {Thread.currentThread ()} je spustený.")}

Keď použijeme Dispečeri. Predvolené alebo GlobalScope.launch vytvoríme coroutine najvyššej úrovne. Aj keď je ľahký, počas behu stále spotrebúva niektoré pamäťové zdroje.

Namiesto toho, aby sme spúšťali coroutiny v GlobalScope, rovnako ako zvyčajne s vláknami (vlákna sú vždy globálne), môžeme spúšťať coroutiny v konkrétnom rozsahu operácie, ktorú vykonávame:

runBlocking {val job = launch {println ("$ {Thread.currentThread ()} has run.")}}

V tomto prípade začneme nový korutín vnútri runBlocking program na vytváranie korutín (ktorý si popíšeme neskôr) bez určenia kontextu. Corutín teda bude dediť runBlockingKontext.

3.3. async

Ďalšou funkciou, ktorú Kotlin poskytuje na vytvorenie korutínu, je async.

The async funkcia vytvorí nový postup a vráti budúci výsledok ako inštanciu Odložené:

val deferred = async {[chránený e-mailom] "$ {Thread.currentThread ()} je spustený." }

odložené je neblokujúca zrušiteľná budúcnosť, ktorá popisuje objekt, ktorý slúži ako proxy server pre výsledok, ktorý je pôvodne neznámy.

Páči sa mi to spustiť, môžeme určiť kontext, v ktorom sa má vykonať korutín, ako aj možnosť spustenia:

val deferred = async (Dispatchers.Unconfined, CoroutineStart.LAZY) {println ("$ {Thread.currentThread ()} je spustený.")}

V tomto prípade sme spustili korutín pomocou Dispečeri.Bez obmedzenia ktorý spúšťa korutíny vo vlákne volajúceho, ale iba do prvého bodu pozastavenia.

Poznač si to Dispečeri.Bez obmedzenia je vhodný, keď korutín nespotrebúva čas procesora ani neaktualizuje žiadne zdieľané údaje.

Okrem toho poskytuje Kotlin Dispečeri.IO ktorý používa zdieľaný fond vlákien vytvorených na požiadanie:

val deferred = async (Dispatchers.IO) {println ("$ {Thread.currentThread ()} has run.")}

Dispečeri.IO sa odporúča, keď potrebujeme vykonávať intenzívne I / O operácie.

3.4. runBlocking

Boli sme sa na to pozrieť skôr runBlocking, ale teraz si o tom poďme povedať podrobnejšie.

runBlocking je funkcia, ktorá spúšťa nový korutín a blokuje aktuálne vlákno až do jeho dokončenia.

Ako príklad v predchádzajúcom úryvku sme spustili program, ale nikdy sme nečakali na výsledok.

Aby sme čakali na výsledok, musíme zavolať čakať () metóda pozastavenia:

// asynchrónny kód ide sem runBlocking {val výsledok = deferred.await () println (výsledok)}

čakať () sa nazýva funkcia pozastavenia. Funkcie pozastavenia je možné volať iba z korutínu alebo inej funkcie pozastavenia. Z tohto dôvodu sme ho uzavreli do a runBlocking vzývanie.

Používame runBlocking v hlavný funkcie a v testoch, aby sme mohli prepojiť blokujúci kód s iným napísaným pozastavovacím štýlom.

Podobným spôsobom, ako sme to urobili v iných programoch na vytváranie korutín, môžeme nastaviť kontext vykonávania:

runBlocking (newSingleThreadContext ("dedicatedThread")) {val výsledok = deferred.await () println (výsledok)}

Všimnite si, že môžeme vytvoriť nové vlákno, v ktorom by sme mohli vykonať korutín. Vyhradené vlákno je však drahý zdroj. A keď už to nie je potrebné, mali by sme ich uvoľniť alebo ešte lepšie znova použiť v celej aplikácii.

4. Záver

V tomto tutoriáli sme sa naučili, ako spustiť asynchrónny neblokujúci kód vytvorením vlákna.

Ako alternatívu k vláknu sme tiež videli, ako je Kotlinov prístup k používaniu korutín jednoduchý a elegantný.

Ako obvykle sú všetky ukážky kódu zobrazené v tomto výučbe dostupné na stránkach Github.