Lekce 1 - Úvod do coroutines v Kotlin
Vítám vás u prvního dílu našeho tutoriálu zaměřeného na Kotlin coroutines. V průběhu našeho e-learningového kurzu si vysvětlíme, co to jsou coroutines a k čemu slouží a ukážeme si, jak s nimi pracovat.
Minimální požadavky kurzu
Pro úspěšné absolvování stávajícího kurzu je třeba znalost jazyka Kotlin na úrovni objektově orientovaného programování včetně základních znalostí práce s kolekcemi v Kotlin. Výhodou je i znalost tvorby vícevláknových aplikaci.
Poslední odkaz vás přesměruje na Java kurz o práci s vlákny. K té Kotlin může použít připravené Java třídy nebo právě alternativní způsob tvorby asynchronních aplikací pomocí coroutines.
V našem kurzu si mnohde ukážeme příklad s použitím vláken a druhý, který využívá coroutines, abychom mohli porovnat efektivitu obou řešení.
Definice coroutines a problém synchronnosti
V oficiální Kotlin dokumentaci jsou coroutines definovány takto:
Coroutine je instancí pozastavitelné operace. Je koncepčně podobná vláknu v tom smyslu, že ke spuštění bere blok kódu, jenž funguje souběžně se zbytkem kódu. Coroutine však není vázána na žádné konkrétní vlákno. Může pozastavit své provádění v jednom vlákně a obnovit jej v jiném.
Coroutines lze považovat za lehká vlákna, ale existuje řada důležitých rozdílů, díky kterým se jejich použití velmi liší od vláken.
Abychom tuto definici a motiv, proč byly v Kotlin coroutines vytvořeny, pochopili, uveďme si jednoduchý příklad.
Motivační příklad
Představme si soubor, kde máme uložená například čísla v následujícím formátu:
1 45 32 78 22
V naší aplikaci budeme chtít sečíst všechna čísla v tomto souboru. Bez znalosti coroutines bychom postupovali tak, že bychom nejprve načetli všechna čísla do kolekce, kterou bychom následně cyklem procházeli a jednotlivé položky sčítali. Mohlo by se zdát, že tento přístup je bez problému, a do určité míry takto opravdu můžeme v aplikaci pracovat. V některých případech by ale takový postup byl velmi pomalý a aplikaci by nadměrně zatěžoval.
Teď se určitě ptáte, jak bychom mohli urychlit vykonávaní programu, když pouze přečteme data ze souboru a následně je sečteme. Postup je velice jednoduchý. Přečteme číslo ze souboru a současně ho přičteme k již načteným.
Rozdíl je nepatrný a u jednoduchých úkonů se to může zdát zbytečné, protože to nepřinese o moc větší výkon programu. Avšak takový postup se už vyplatí například v případě, že se nejedná o jednoduchou operaci sčítání, ale třeba nalezení všech prvočísel v poli čísel. Tato operace je totiž už o něco náročnější na prostředky počítače. Pokud budeme hledat prvočísla již při načítání dat, dokážeme tak výrazně zlepšit výkon aplikace.
Coroutines slouží právě k tomu, abychom mohli zároveň číst a zpracovávat data.
V souvislosti s coroutines mluvíme o tzv. asynchronním programování. Tento termín označuje skutečnost, kdy je kód v coroutine bloku vykonán mimo běh hlavní linie programu.
První coroutine projekt v Kotlin
Abychom pochopili co nejlépe celou problematiku, ukážeme si vše na názorném příkladu. Vytvoříme si první jednoduchý coroutine projekt.
Otevřeme si IntelliJ IDEA, klikneme na New Project a otevře se nám nové okno, které vyplníme následovně:

Poté klikneme na Create, tím se nám vytvoří a otevře nový projekt.
Přidání závislosti
Coroutines implementuje externí knihovna kotlinx.coroutines
.
Než budeme moci pokračovat, budeme ji muset přidat jako novou závislost do
souboru build.gradle
. Tím řekneme kompilátoru, že budeme
používat coroutines.
Otevřeme si soubor build.gradle
a do bloku
dependencies
přidáme následující řádek kódu:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
Výsledný soubor pak vypadá následovně:
plugins { id 'org.jetbrains.kotlin.jvm' version '1.8.0' id 'application' } group = 'org.example' version = '1.0-SNAPSHOT' repositories { mavenCentral() } dependencies { testImplementation 'org.jetbrains.kotlin:kotlin-test' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" } test { useJUnitPlatform() } kotlin { jvmToolchain(8) } application { mainClassName = 'MainKt' }
Jiné balíčkovací systémy mají nastavení závislostí trochu odlišné. Stejně tak se postupně vyvíjejí jejich nové verze. Všechny potřebné informace k přidání coroutines do projektu jsou uvedeny v oficiální dokumentaci knihovny.
Metoda main()
Máme přidané veškeré závislosti a můžeme začít psát náš kód.
Otevřeme si soubor Main.kt
a upravíme v něm metodu
main()
. Abychom mohli pracovat s coroutines, přepíšeme ji
následovně:
import kotlinx.coroutines.runBlocking fun main(): Unit = runBlocking { }
Co znamená blok runBlocking
si podrobněji vysvětlíme někdy
později. Pro potřeby dnešního dílu nám bude bohatě stačit, že se jedná
o most, který nás dostane ze světa bez coroutines do světa
s coroutines. Dnes si ukážeme a vysvětlíme plné základy
coroutines, které musíme pochopit. Později se pak dostaneme i k optimalizaci
problémů, které jsme si uvedli na začátku lekce.
Launch
a tvorba první
coroutine
Pojďme si tedy vytvořit naši první coroutine. Metodu main()
doplníme následovně:
fun main(): Unit = runBlocking { launch { repeat(3) { println("Hello $it") } } }
V kódu můžeme vidět blok launch
, který slouží ke
tvorbě coroutines. Jak přesně funguje, se dozvíme za malý
moment. Pokud spustíme tento program, dostaneme následující výstup:
Hello 0 Hello 1 Hello 2
Nic neočekávaného pro většinu z nás.
Přidání druhé coroutine
A co se stane, když přidáme další blok launch
? Můžeme si
to hned vyzkoušet. Kód upravíme následovně:
fun main(): Unit = runBlocking { launch { repeat(3) { println("Hello $it") } } launch { repeat(3) { println("World $it") } } }
Když opět program spustíme, dostaneme následující výstup:
Hello 0 Hello 1 Hello 2 World 0 World 1 World 2
Opět nic divného. Tedy k čemu nám slouží launch
a
coroutines? Podobně jako při práci s vlákny používáme
Thread
, tak coroutines (a builder launch
) slouží k
tomu, abychom mohli tvořit dvě rutiny, které běží
paralelně.
Mezi coroutines a Thread
je jeden veliký rozdíl.
Pokud bychom vytvořili sto tisíc vláken za pomocí Thread
, brzo
by nám došla paměť. Ale pokud vytvoříme sto tisíc jednotlivých
coroutines, tak nám většinou paměť nedojde. O coroutines lze přemýšlet
jako o lehkých vláknech, tedy vláknech nenáročných na
paměť, procesor atd.
Blok launch
nám slouží k tomu, abychom vytvořili toto lehké
vlákno. Kód, který jsme před chvilkou napsali, spustí dvě couroutines,
které dokáží běžet paralelně. Proč tedy výstup vypadá, jak kdyby nebyl
kód vykonáván paralelně? Tento problém je o něco složitější a souvisí
s tzv. dispatchers (dispečery), o kterých si budeme povídat v dalších
dílech.
Funkce delay()
Abychom dokázali, že coroutines běží paralelně, použijeme funkci
delay()
. Jedná se o verzi metody Thread.sleep()
,
která je určená pro coroutines. Jednoduše zastavíme vykonávání dané
coroutine na uvedený počet milisekund. Kód upravíme následovně:
fun main(): Unit = runBlocking { launch { repeat(3) { println("Hello $it") // Pozastavit vykonávání programu na 0.5 s delay(500) } } launch { repeat(3) { println("World $it") // Pozastavit vykonávání programu na 1.5 s delay(1500) } } }
Pokud tento kód spustíme, dostaneme výstup podobný tomuto:
Hello 0 World 0 Hello 1 Hello 2 World 1 World 2
Záměrně jsem použil formulaci "výstup podobný tomuto", protože coroutines mohou běžet v různém pořadí.
Pro rekapitulaci dnešní lekce si shrneme jednotlivé informace. Blok
launch
nám slouží k vytvoření coroutine, tedy lehkého
vlákna, které může běžet paralelně s hlavním vláknem. Funkce
delay()
nám slouží k pozastavení výkonu coroutine na zadaný
počet milisekund. Toto nám bude pro dnešek stačit.
V příští lekci, Tvorba coroutines v Kotlin, si uvedeme jednotlivé možnosti tvorby coroutines a na praktických příkladech si ukážeme, jaký je mezi nimi rozdíl.
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 (113.27 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin