1. diel - Úvod do coroutines v Kotline
Vítam vás pri prvom dieli nášho tutoriálu zameraného na Kotlin coroutines. V priebehu nášho e-learningového kurzu si vysvetlíme, čo to sú coroutines ak čomu slúži a ukážeme si, ako s nimi pracovať.
Minimálne požiadavky kurzu
Pre úspešné absolvovanie súčasného kurzu je potrebná znalosť jazyka Kotlin na úrovni objektovo orientovaného programovania vrátane základných znalostí práce s kolekciami v Kotline. Výhodou je aj znalosť tvorby viacvláknových aplikácií.
Posledný odkaz vás presmeruje na Java kurz o práci s vláknami. K tej Kotlin môže použiť pripravené Java triedy alebo práve alternatívny spôsob tvorby asynchrónnych aplikácií pomocou coroutines.
V našom kurze si mnohokrát ukážeme príklad s použitím vlákien a druhý, ktorý využíva coroutines, aby sme mohli porovnať efektivitu oboch riešení.
Definícia coroutines a problém synchrónnosti
V oficiálnej Kotlin dokumentácii sú coroutines definované takto:
Aby sme túto definíciu a motív, prečo boli v Kotline coroutines vytvorené, pochopili, uveďme si jednoduchý príklad.Coroutine je inštanciou pozastaviteľnej operácie. Je koncepčne podobná vláknu v tom zmysle, že na spustenie berie blok kódu, ktorý funguje súbežne so zvyškom kódu. Coroutine však nie je viazaná na žiadne konkrétne vlákno. Môže pozastaviť svoje vykonávanie v jednom vlákne a obnoviť ho v inom.
Coroutines možno považovať za ľahké vlákna, ale existuje množstvo dôležitých rozdielov, vďaka ktorým sa ich použitie veľmi líši od vlákien.
Motivačný príklad
Predstavme si súbor, kde máme uložené napríklad čísla v nasledujúcom formáte:
1 45 32 78 22
V našej aplikácii budeme chcieť sčítať všetky čísla v tomto súbore. Bez znalosti coroutines by sme postupovali tak, že by sme najprv načítali všetky čísla do kolekcie, ktorou by sme následne cyklom prechádzali a jednotlivé položky sčítali. Mohlo by sa zdať, že tento prístup je bez problémov, a do určitej miery takto naozaj môžeme v aplikácii pracovať. V niektorých prípadoch by ale takýto postup bol veľmi pomalý a aplikáciu by nadmerne zaťažoval.
Teraz sa určite pýtate, ako by sme mohli urýchliť vykonávanie programu, keď iba prečítame dáta zo súboru a následne ich sčítame. Postup je veľmi jednoduchý. Prečítame číslo zo súboru a súčasne ho pripočítame k už načítaným.
Rozdiel je nepatrný a pri jednoduchých úkonoch sa to môže zdať zbytočné, pretože to neprinesie oveľa väčší výkon programu. Avšak takýto postup sa už oplatí napríklad v prípade, že nejde o jednoduchú operáciu sčítania, ale napríklad nájdenie všetkých prvočísel v poli čísel. Táto operácia je totiž už o niečo náročnejšia na prostriedky počítača. Pokiaľ budeme hľadať prvočísla už pri načítaní dát, dokážeme tak výrazne zlepšiť výkon aplikácie.
Coroutines slúži práve na to, aby sme mohli zároveň čítať a spracovávať dáta.
V súvislosti s coroutines hovoríme o tzv. asynchrónnom programovaní. Tento termín označuje skutočnosť, kedy je kód v coroutine bloku vykonaný mimo beh hlavnej línie programu.
Prvý coroutine projekt v Kotline
Aby sme pochopili čo najlepšie celú problematiku, ukážeme si všetko na názornom príklade. Vytvoríme si prvý jednoduchý Coroutine projekt.
Otvoríme si IntelliJ IDEA, klikneme na New Project a otvorí sa nám nové okno, ktoré vyplníme nasledovne:
Potom klikneme na Create, tým sa nám vytvorí a otvorí nový projekt.
Pridanie závislosti
Coroutines implementuje externá knižnica kotlinx.coroutines
.
Než budeme môcť pokračovať, budeme ju musieť pridať ako novú závislosť
do súboru build.gradle
. Tým povieme kompilátoru, že budeme
používať coroutines.
Otvoríme si súbor build.gradle
a do bloku
dependencies
pridáme nasledujúci riadok kódu:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
Výsledný súbor potom vyzerá nasledovne:
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' }
Iné balíčkovacie systémy majú nastavenie závislostí trochu odlišné. Rovnako sa postupne vyvíjajú ich nové verzie. Všetky potrebné informácie na pridanie coroutines do projektu sú uvedené voficiálnej dokumentácii knižnice.
Metóda main()
Máme pridané všetky závislosti a môžeme začať písať náš kód.
Otvoríme si súbor Main.kt
a upravíme v ňom metódu
main()
. Aby sme mohli pracovať s coroutines, prepíšeme ju
nasledovne:
import kotlinx.coroutines.runBlocking fun main(): Unit = runBlocking { }
Čo znamená blok runBlocking
si podrobnejšie vysvetlíme
niekedy neskôr. Pre potreby dnešného dielu nám bude bohato stačiť, že sa
jedná o most, ktorý nás dostane zo sveta bez coroutines do
sveta s coroutines. Dnes si ukážeme a vysvetlíme plné
základy coroutines, ktoré musíme pochopiť. Neskôr sa potom dostaneme aj k
optimalizácii problémov, ktoré sme si uviedli na začiatku lekcie.
Launch
a tvorba prvej
coroutine
Poďme si teda vytvoriť našu prvú coroutine. Metódu main()
doplníme nasledovne:
fun main(): Unit = runBlocking { launch { repeat(3) { println("Hello $it") } } }
V kóde môžeme vidieť blok launch
, ktorý slúži na
tvorbu coroutines. Ako presne funguje, sa dozvieme za malý
moment. Ak spustíme tento program, dostaneme nasledujúci výstup:
Hello 0 Hello 1 Hello 2
Nič neočakávaného pre väčšinu z nás.
Pridanie druhej coroutine
A čo sa stane, keď pridáme ďalší blok launch
? Môžeme si
to hneď vyskúšať. Kód upravíme nasledovne:
fun main(): Unit = runBlocking { launch { repeat(3) { println("Hello $it") } } launch { repeat(3) { println("World $it") } } }
Keď opäť program spustíme, dostaneme nasledujúci výstup:
Hello 0 Hello 1 Hello 2 World 0 World 1 World 2
Opäť nič divné. Teda na čo nám slúži launch
a
coroutines? Podobne ako pri práci s vláknami používame Thread
,
tak coroutines (a builder launch
) slúžia k tomu, aby sme mohli
tvoriť dve rutiny, ktoré bežia paralelne.
Medzi coroutines a Thread
je jeden veľký rozdiel.
Ak by sme vytvorili sto tisíc vlákien pomocou Thread
, čoskoro by
nám došla pamäť. Ale pokiaľ vytvoríme sto tisíc jednotlivých coroutines,
tak nám väčšinou pamäť nedôjde. O coroutines je možné premýšľať ako
o ľahkých vláknach, teda vláknach nenáročných na
pamäť, procesor atď.
Blok launch
nám slúži na to, aby sme vytvorili toto ľahké
vlákno. Kód, ktorý sme pred chvíľkou napísali, spustia dve couroutines,
ktoré dokážu bežať paralelne. Prečo teda výstup vyzerá, ako keby nebol
kód vykonávaný paralelne? Tento problém je o niečo zložitejší a súvisí
s tzv. dispatchers (dispečermi), o ktorých sa budeme rozprávať v ďalších
dieloch.
Funkcia delay()
Aby sme dokázali, že coroutines beží paralelne, použijeme funkciu
delay()
. Ide o verziu metódy Thread.sleep()
, ktorá
je určená pre coroutines. Jednoducho zastavíme vykonávanie danej coroutine
na uvedený počet milisekúnd. Kód upravíme nasledovne:
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) } } }
Ak tento kód spustíme, dostaneme výstup podobný tomuto:
Hello 0 World 0 Hello 1 Hello 2 World 1 World 2
Zámerne som použil formuláciu "výstup podobný tomuto", pretože coroutines môžu bežať v rôznom poradí.
Na rekapituláciu dnešnej lekcie si zhrnieme jednotlivé informácie. Blok
launch
nám slúži na vytvorenie coroutine, teda ľahkého
vlákna, ktoré môže bežať paralelne s hlavným vláknom. Funkcia
delay()
nám slúži na pozastavenie výkonu coroutine na zadaný
počet milisekúnd. Toto nám bude pre dnešok stačiť.
V budúcej lekcii, Tvorba Coroutines v Kotline , si uvedieme jednotlivé možnosti tvorby coroutines a na praktických príkladoch si ukážeme, aký je medzi nimi rozdiel.
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 (113.27 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin