IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

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

 

Předchozí článek
Úvod do coroutines v Kotlin
Všechny články v sekci
Coroutines v Kotlin
Přeskočit článek
(nedoporučujeme)
Výkon coroutine aplikací v Kotlin
Článek pro vás napsal Marek Urbańczyk
Avatar
Uživatelské hodnocení:
4 hlasů
Autor se věnuje programování v Kotlinu, Javě. Má také zkušenosti s C#.
Aktivity