Lekce 2 - Tvorba coroutines v Kotlin
V minulé lekci, Úvod do coroutines v Kotlin, jsme si představili svět coroutines. Vysvětlili jsme si, v čem se liší od vláken a ukázali si, jak je tvořit a jak je pozastavit na zadaný čas.
Vítám vás u dalšího dílu tutoriálu zaměřeného na Kotlin coroutines, kde se společně učíme tvořit paralelní aplikace. V dnešním dílu si ukážeme, jakým způsobem můžeme tvořit coroutines. Možností máme totiž hned několik. Dnes si všechny probereme a vysvětlíme.
Způsoby tvoření coroutines v Kotlin
Při tvorbě coroutines máme na výběr celkem ze tří možností:
- K vytváření jednoduchých coroutines použijeme blok
launch
. - Pomocí bloku
async
tvoříme coroutines, které vrací nějakou hodnotu. - Pro tvorbu coroutines můžeme také použít samotný blok
runBlocking
, kterým jinak obalujeme blokylaunch
aasync
.
Blok runBlocking
Blok runBlocking
, jak již víme, slouží jako most mezi
světem bez coroutines a světem s coroutines. Používáme ho obvykle buď v
metodě main()
, nebo v testovacím
kódu. Princip jeho fungování je jednoduchý. Zablokuje
thread
, ze kterého jej voláme a vytvoří novou coroutine. V té
pak můžeme například volat funkci delay()
.
Příklad se samotným
runBlocking
Pojďme si použití samotného runBlocking
ukázat na
jednoduchém příkladu. Vytvoříme si nový projekt a v souboru
build.gradle
doplníme potřebné závislosti, jak jsme si ukázali
v lekci Úvod do
coroutines v Kotlin.
Poté se přesuneme do souboru Main.kt
, kde upravíme metodu
main()
následující implementací:
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") }
Poté, co tento kód spustíme, dostaneme následující výstup:
Před runBlocking V runBlocking Po runBlocking
Na začátku metody main()
vypíšeme do konzole
Před runBlocking
. Následně použijeme blok
runBlocking
, který vytvoří coroutine. V něm pak zavoláme
delay(1000)
. Po jedné sekundě se vypíše
V runBlocking
. Potom se ihned vypíše
Po runBlocking
.
Někteří z vás se možná ptají, proč se nevypíše
Po runBlocking
dříve než V runBlocking
? Víme již
totiž, že runBlocking
vytvoří coroutine, která se
vykonává paralelně. Vysvětlení je jednoduché. Blok
runBlocking
zablokuje hlavní vlákno (vlákno
metody main()
) do té doby, než se dokončí coroutine v
runBlocking
.
Pro lepší pochopení upravme náš příklad následovně:
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 spuštění 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
Zde jde jasně vidět, jak blok runBlocking
blokuje spuštění
posledního výpisu.
Blok launch
Tvoření coroutines pomocí launch
jsme si už ukázali minule.
Zopakujme si tedy, co přesně blok launch
dělá. Vytvoří
coroutine, která se bude vykonávat paralelně.
Příklad launch
s
runBlocking
Co se stane, když použijeme launch
v bloku
runBlocking
?
Ukažme si jednoduchý pří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čátku tvoříme coroutine, kterou
hned pozastavíme na dvě sekundy. Když program spustíme, dostaneme
následující 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
Pokud sledujeme výstup pořádně, může nám připadat, že průběh
programu nedává moc smysl. Do konzole se vypíše
Přestávám blokovat main thread
, ale stále chvilku trvá než se
vypíše Po runBlocking
. To je způsobeno tím, že
runBlocking
bude také blokovat hlavní vlákno do
doby, než se vykonají všechny coroutines, které vytvoříme
uvnitř bloku.
Blok launch
a Job
Představme si složitější aplikaci, která bude obsahovat coroutines. Určitě se nám stane, že v ní budeme chtít spustit určitou coroutine až po vykonání ostatních coroutines.
My si dnes vytvoříme pouze jednoduchý příklad a v něm tři coroutines:
coroutine 1
, coroutine 2
a coroutine 3
.
Budeme chtít, aby se coroutine 3
vykonala až potom, co se
dokončí coroutine 1
a coroutine 2
. K tomu nám
slouží rozhraní Job
.
Každé volání launch
v sobě nese díky
rozhraní Job
stav vykonávání coroutine.
Náš příklad bude zatím obsahovat tři coroutines. První uspíme na
sekundu, druhé nastavíme delay()
dvě sekundy:
fun main(): Unit = runBlocking { launch { delay(1000) println("coroutine 1") } launch { delay(2000) println("coroutine 2") } launch { println("coroutine 3") } }
Jak asi tušíme, dostaneme následující výstup:
coroutine 3 coroutine 1 coroutine 2
My jsme ale chtěli, aby se coroutine 3
vykonala až po
coroutine 1
a coroutine 2
. Pro dosažení tohoto cíle
uložíme stav prvních dvou coroutines do proměnných firstJob
a
secondJob
a zavoláme metodu join()
.
Právě pomocí metody join()
zajistíme, že
program počká, než se daná coroutine dokončí.
Náš příklad upravíme následovně:
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ávný:
coroutine 1 coroutine 2 coroutine 3
Vidíme, že se nyní naše coroutine 3
vykoná jako
poslední.
Blok async
pro tvorbu
coroutines
V úvodu jsme si prozradili, že blok async
je podobný bloku
launch
, ale vrací nějakou hodnotu. Je to tím, že místo
rozhraní Job
používá rozhraní Deffered
.
Pokud bychom se podívali do dokumentace, zjistili bychom, že
Deffered
z rozhraní Job
dědí.
Ukažme si poslední příklad, ve kterém coroutine ponese nějakou hodnotu.
Tuto hodnotu pak použijeme, v našem příkladu pouze sečteme dvě čísla a
vypíšeme výsledek. V kódu vytvoříme dvě coroutines pomocí 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ždém bloku async
vracíme hodnotu. První coroutine nese
hodnotu 10
a druhá hodnotu 20
. Oběma coroutines jsme
nastavili krátký delay()
, abychom simulovali déle trvající
výpočet. Proto při výpisu voláme metodu await()
, která nám
vrátí hodnotu až po vykonání coroutine.
Když aplikaci spustíme, zobrazí se nám požadovaný výsledek:
Výsledek: 30
V tomto díle jsme si ukázali, jak můžeme tvořit coroutines. Už víme
jak fungují bloky runBlocking
, launch
a
async
a jaký je mezi nimi rozdíl.
V příští lekci, Výkon coroutine aplikací v Kotlin, si ukážeme, jak coroutines zlepšují výkon
aplikace. K porovnání si vytvoříme praktické příklady a zopakujeme si v
nich základní práci s launch
, async
a
runBlocking
.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 2x (317.39 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin