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
async
tvoríme coroutines, ktoré vracajú nejakú hodnotu. - Na tvorbu coroutines môžeme tiež použiť samotný blok
runBlocking
, ktorým inak obaľujeme blokylaunch
aasync
.
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é 2x (317.39 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin