2. diel - Tvorba Coroutines v Kotline
V minulej lekcii, Úvod do coroutines v Kotline , sme si predstavili svet coroutines. Vysvetlili sme si, v čom sa líšia od vlákien a ukázali si, ako ich tvoriť a ako ich pozastaviť na zadaný čas.
Vítam vás pri ďalšom diele tutoriálu zameraného na Kotlin coroutines, kde sa spoločne učíme tvoriť paralelné aplikácie. V dnešnom dieli si ukážeme, akým spôsobom môžeme tvoriť coroutines. Možností máme totiž hneď niekoľko. Dnes si všetky preberieme a vysvetlíme.
Spôsoby tvorenia coroutines v Kotline
Pri tvorbe coroutines máme na výber celkom z troch možností:
- Na vytváranie jednoduchých coroutines použijeme blok
launch. - Pomocou bloku
asynctvoríme coroutines, ktoré vracajú nejakú hodnotu. - Na tvorbu coroutines môžeme tiež použiť samotný blok
runBlocking, ktorým inak obaľujeme blokylaunchaasync.
runBlocking
Blok runBlocking, ako už vieme, slúži ako most medzi svetom
bez coroutines a svetom s coroutines. Používame ho obvykle buď v
metóde main(), alebo v testovacom
kóde. Princíp jeho fungovania je jednoduchý. Zablokuje
thread, z ktorého ho voláme a vytvorí novú coroutine. V tej
potom môžeme napríklad volať funkciu delay().
Príklad so samotným
runBlocking
Poďme si použitie samotného runBlocking ukázať na
jednoduchom príklade. Vytvoríme si nový projekt av súbore
build.gradle doplníme potrebné závislosti, ako sme si ukázali v
lekcii Úvod do
coroutines v Kotline.
Potom sa presunieme do súboru Main.kt, kde upravíme metódu
main() nasledujúcu implementáciu:
import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking fun main(args: Array<String>) { println("Před runBlocking") runBlocking { delay(1000) println("V runBlocking") } println("Po runBlocking") }
Potom, čo tento kód spustíme, dostaneme nasledujúci výstup:
Před runBlocking V runBlocking Po runBlocking
Na začiatku metódy main() vypíšeme do konzoly
Před runBlocking. Následne použijeme blok
runBlocking, ktorý vytvorí coroutine. V ňom potom zavoláme
delay(1000). Po jednej sekunde sa vypíše
V runBlocking. Potom sa ihneď vypíše
Po runBlocking.
Niektorí z vás sa možno pýtajú, prečo sa nevypíše
Po runBlocking skôr ako V runBlocking ? Vieme už
totiž, že runBlocking vytvorí coroutine, ktorá sa
vykonáva paralelne. Vysvetlenie je jednoduché. Blok
runBlocking zablokuje hlavné vlákno (vlákno
metódy main()) do tej doby, než sa dokončí coroutine v
runBlocking.
Pre lepšie pochopenie upravme náš príklad nasledovne:
fun main(args: Array<String>) { println("Před runBlocking") runBlocking { println("Blokuji main thread") println("Počkám 1 sekundu") delay(1000) println("Přestávám blokovat main thread") } println("Po runBlocking") }
Po spustení programu dostaneme tento výstup:
Před runBlocking Blokuji main thread Počkám 1 sekundu Přestávám blokovat main thread Po runBlocking
Tu sa dá jasne vidieť, ako blok runBlocking blokuje spustenie
posledného výpisu.
Blok launch
Tvorenie coroutines pomocou launch sme si už ukázali minule.
Zopakujme si teda, čo presne blok launch robí. Vytvorí
coroutine, ktorá sa bude vykonávať paralelne.
Príklad launch s
runBlocking
Čo sa stane, keď použijeme launch v bloku
runBlocking ?
Ukážme si jednoduchý príklad:
fun main(args: Array<String>) { println("Před runBlocking") runBlocking { launch { delay(2000) println("Výstup z launch") } println("Blokuji main thread") println("Počkám 1 sekundu") delay(1000) println("Přestávám blokovat main thread") } println("Po runBlocking") }
V bloku runBlocking na začiatku tvoríme coroutine, ktorú
hneď pozastavíme na dve sekundy. Keď program spustíme, dostaneme
nasledujúci výstup:
Před runBlocking Blokuji main thread Počkám 1 sekundu Přestávám blokovat main thread Výstup z launch Po runBlocking
Pokiaľ sledujeme výstup poriadne, môže nám pripadať, že priebeh
programu nedáva veľmi zmysel. Do konzoly sa vypíše
Přestávám blokovat main thread, ale stále chvíľku trvá než
sa vypíše Po runBlocking. To je spôsobené tým , že
runBlocking bude tiež blokovať hlavné vlákno
do doby , než sa vykonajú všetky coroutines, ktoré
vytvoríme vo vnútri bloku.
Blok launch a Job
Predstavme si zložitejšiu aplikáciu, ktorá bude obsahovať coroutines. Určite sa nám stane, že v nej budeme chcieť spustiť určitú coroutine až po vykonaní ostatných coroutines.
My si dnes vytvoríme iba jednoduchý príklad av ňom tri coroutines:
coroutine 1, coroutine 2 a coroutine 3.
Budeme chcieť, aby sa coroutine 3 vykonala až potom, čo sa
dokončí coroutine 1 a coroutine 2. K tomu nám
slúži rozhranie Job.
Každé volanie launch v sebe nesie vďaka
rozhraniu Job stav vykonávania coroutine.
Náš príklad bude zatiaľ obsahovať tri coroutines. Prvé uspíme na
sekundu, druhé nastavíme delay() dve sekundy:
fun main(): Unit = runBlocking { launch { delay(1000) println("coroutine 1") } launch { delay(2000) println("coroutine 2") } launch { println("coroutine 3") } }
Ako asi tušíme, dostaneme nasledujúci výstup:
coroutine 3 coroutine 1 coroutine 2
My sme ale chceli, aby sa coroutine 3 vykonala až po
coroutine 1 a coroutine 2. Na dosiahnutie tohto cieľa
uložíme stav prvých dvoch coroutines do premenných firstJob a
secondJob a zavoláme metódu join().
Práve pomocou metódy join() zaistíme, že
program počká, kým sa daná coroutine dokončí.
Náš príklad upravíme nasledovne:
fun main(): Unit = runBlocking { val firstJob = launch { delay(1000) println("coroutine 1") } val secondJob = launch { delay(2000) println("coroutine 2") } firstJob.join() secondJob.join() launch { println("coroutine 3") } }
Výstup už bude správny:
coroutine 1 coroutine 2 coroutine 3
Vidíme, že sa teraz naša coroutine 3 vykoná ako
posledná.
Blok async na tvorbu
coroutines
V úvode sme si prezradili, že blok async je podobný bloku
launch, ale vracia nejakú hodnotu. Je to tým, že namiesto
rozhrania Job používa rozhranie Deffered.
Ak by sme sa pozreli do dokumentácie, zistili by sme, že
Deffered z rozhrania Job dedí.
Ukážme si posledný príklad, v ktorom coroutine ponesie nejakú hodnotu.
Túto hodnotu potom použijeme, v našom príklade iba sčítame dve čísla a
vypíšeme výsledok. V kóde vytvoríme dve coroutines pomocou bloku
async:
fun main(): Unit = runBlocking { val firstDeffered = async { delay(1000) 10 } val secondDeffered = async { delay(1500) 20 } println("Výsledek: ${firstDeffered.await() + secondDeffered.await()}") }
V každom bloku async vraciame hodnotu. Prvá coroutine nesie
hodnotu 10 a druhá hodnotu 20. Obom coroutines sme
nastavili krátky delay(), aby sme simulovali dlhšie trvajúci
výpočet. Preto pri výpise voláme metódu await(), ktorá nám
vráti hodnotu až po vykonaní coroutine.
Keď aplikáciu spustíme, zobrazí sa nám požadovaný výsledok:
Výsledek: 30
V tomto diele sme si ukázali, ako môžeme tvoriť coroutines. Už vieme ako
fungujú bloky runBlocking, launch a
async a aký je medzi nimi rozdiel.
V budúcej lekcii, Výkon coroutine aplikácií v Kotlin , si ukážeme, ako coroutines zlepšujú výkon
aplikácie. Na porovnanie si vytvoríme praktické príklady a zopakujeme si v
nich základnú prácu s launch, async a
runBlocking.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 3x (317.39 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin
