IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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 bloky launch a async.
Blok 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

 

Predchádzajúci článok
Úvod do coroutines v Kotline
Všetky články v sekcii
Coroutines v Kotline
Preskočiť článok
(neodporúčame)
Výkon coroutine aplikácií v Kotlin
Článok pre vás napísal Marek Urbańczyk
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje programování v Kotlinu, Javě. Má také zkušenosti s C#.
Aktivity