Threads vs Coroutines Kotlinban

1. Bemutatkozás

Ebben a gyors bemutatóban szálakat fogunk létrehozni és végrehajtani Kotlinban.

Később megbeszéljük, hogyan kerülhetnénk el teljesen, a Kotlin Coroutines javára.

2. Szálak létrehozása

Egy szál létrehozása Kotlinban hasonló a Java-hoz.

Vagy meghosszabbíthatnánk a cérna osztály (bár ez nem ajánlott, mivel Kotlin nem támogatja a többszörös öröklést):

class SimpleThread: Thread () {public override fun run () {println ("$ {Thread.currentThread ()} futott.")}}

Vagy megvalósíthatjuk a Futható felület:

class SimpleRunnable: Futható {public override fun run () {println ("$ {Thread.currentThread ()} futott.")}}

És ugyanúgy, mint a Java-ban, végre tudjuk hajtani a Rajt() módszer:

val thread = SimpleThread () thread.start () val threadWithRunnable = Thread (SimpleRunnable ()) threadWithRunnable.start ()

Alternatív megoldásként, hasonlóan a Java 8-hoz, a Kotlin is támogatja a SAM konverziókat, ezért kihasználhatjuk azt és átadhatunk egy lambdát:

val thread = Szál {println ("$ {Thread.currentThread ()} futott.")} thread.start ()

2.2. Kotlin cérna() Funkció

Egy másik módszer a funkció figyelembevétele cérna() hogy Kotlin biztosítja:

szórakoztató szál (kezdet: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, név: String? = null, prioritás: Int = -1, blokk: () -> Unit): Thread

Ezzel a funkcióval egy szál egyszerűsíthető és végrehajtható egyszerűen:

szál (start = true) {println ("$ {Thread.currentThread ()} futott.")}

A függvény öt paramétert fogad el:

  • Rajt - Azonnal futtatni a szálat
  • isDaemon - A szál létrehozása démonszálként
  • contextClassLoader - Osztályterhelő osztályok és erőforrások betöltéséhez
  • név - A szál nevének beállításához
  • kiemelten fontos - A szál prioritásának beállítása

3. Kotlin Coroutines

Csábító azt gondolni, hogy több szál ívása segíthet abban, hogy több feladatot egyidejűleg hajtsunk végre. Sajnos ez nem mindig igaz.

Túl sok szál létrehozása valójában alulteljesít az alkalmazás bizonyos helyzetekben; a szálak olyan objektumok, amelyek az objektum kiosztás és a szemétgyűjtés során terhelést jelentenek.

E problémák leküzdése érdekében Kotlin új módszert vezetett be az aszinkron, nem blokkoló kód írására; a Coroutine.

A szálakhoz hasonlóan a koroutinok egyidejűleg futhatnak be, várhatnak és kommunikálhatnak egymással, azzal a különbséggel, hogy azok létrehozása sokkal olcsóbb, mint a szálak.

3.1. Coroutine kontextus

Mielőtt bemutatnánk azokat a korutinépítőket, amelyeket Kotlin biztosít dobozon kívül, meg kell vitatnunk a Coroutine kontextust.

A koroutinok mindig valamilyen kontextusban hajtanak végre, amely különféle elemek összessége.

A fő elemek a következők:

  • Munka - több állapotú lemondható munkafolyamatot modellez, amelynek életciklusa befejeződik
  • Diszpécser - meghatározza, hogy a megfelelő coroutine milyen szálat vagy szálakat használ a végrehajtásához. A diszpécserrel korlátozhatjuk a coroutine végrehajtását egy adott szálra, elküldhetjük egy szálkészletbe, vagy hagyhatjuk korlátlanul futtatni

Majd meglátjuk, hogyan adható meg a kontextus, miközben a következő szakaszokban leírjuk a korutinokat.

3.2. dob

A dob A function egy koroutin készítő, amely egy új koroutint indít anélkül, hogy blokkolná az aktuális szálat és a koroutinra utalást ad vissza a-ként Munka tárgy:

runBlocking {val job = launch (Dispatchers.Default) {println ("$ {Thread.currentThread ()} futott.")}}

Két opcionális paramétere van:

  • kontextus - Az a kontextus, amelyben a koroutint végrehajtják, ha nincs meghatározva, akkor a kontextust örökli a CoroutineScope -tól indul
  • Rajt - A korutin kezdési lehetőségei. Alapértelmezés szerint a koroutin végrehajtását azonnal ütemezik

Ne feledje, hogy a fenti kódot a szálak megosztott háttérkészletébe hajtják végre, mert ezt használtuk Diszpécserek. Alapértelmezés amely elindítja a GlobalScope.

Alternatív megoldásként használhatjuk GlobalScope.launch amely ugyanazt a diszpécsert használja:

val job = GlobalScope.launch {println ("$ {Thread.currentThread ()} futott.")}

Amikor használjuk Diszpécserek. Alapértelmezés vagy GlobalScope.launch létrehozunk egy felső szintű koroutint. Annak ellenére, hogy könnyű, futás közben mégis fogyaszt némi memóriaforrást.

Ahelyett, hogy koruttinokat indítanánk a GlobalScope-ban, csakúgy, mint általában a szálakkal szoktuk (a szálak mindig globálisak), indíthatunk koroutinokat is az általunk végrehajtott művelet konkrét körében:

runBlocking {val job = launch {println ("$ {Thread.currentThread ()} futott.")}}

Ebben az esetben új korutint indítunk a runBlocking coroutine készítő (amelyet később leírunk) a kontextus megadása nélkül. Így a koroutin örökölni fog runBlocking’Kontextusa.

3.3. aszinkron

Egy másik funkció, amelyet Kotlin biztosít egy koroutin létrehozására, az aszinkron.

A aszinkron függvény új koroutint hoz létre, és egy jövőbeli eredményt ad vissza példaként Halasztva:

val deferred = async {[email protected] "$ $ Thread.currentThread ()} futott." }

elhalasztották egy nem blokkolható felmondható jövő, amely leír egy objektumot, amely egy eredetileg ismeretlen eredmény proxyjaként működik.

Mint dob, megadhatunk egy kontextust, amelyben a koroutint végre kell hajtani, valamint egy kezdési opciót:

val deferred = async (Dispatchers.Unconfined, CoroutineStart.LAZY) {println ("$ {Thread.currentThread ()} futott.")}

Ebben az esetben elindítottuk a koroutint a Diszpécserek.Korlátlan amely a hívószálban kezdi a korutint, de csak az első felfüggesztési pontig.

Vegye figyelembe, hogy Diszpécserek.Korlátlan akkor jó, ha egy coroutine nem fogyaszt CPU-időt, és nem frissít semmilyen megosztott adatot.

Ezenkívül Kotlin biztosítja Diszpécserek.IO amely igény szerint létrehozott szálak közös készletét használja:

val deferred = async (Dispatchers.IO) {println ("$ {Thread.currentThread ()} futott.")}

Diszpécserek.IO akkor ajánlott, ha intenzív I / O műveleteket kell végrehajtanunk.

3.4. runBlocking

Korábban megnéztük runBlocking, de most beszéljünk erről mélyebben.

runBlocking egy olyan funkció, amely egy új korutint futtat és blokkolja az aktuális szálat a befejezéséig.

Például az előző részletben elindítottuk a korutint, de soha nem vártuk meg az eredményt.

Ahhoz, hogy megvárjuk az eredményt, felhívnunk kell a várják() felfüggesztési módszer:

// az aszinkron kód ide kerül runBlocking {val eredmény = deferred.await () println (eredmény)}

várják() az úgynevezett felfüggesztési funkció. A felfüggesztés függvényeket csak egy koroutinból vagy más felfüggesztés funkcióból lehet meghívni. Ezért becsatoltuk a runBlocking behívás.

Használunk runBlocking ban ben fő- függvényekben és tesztekben, hogy összekapcsolhassuk a blokkoló kódot más, felfüggesztő stílusban írt kóddal.

Hasonló módon, mint más koroutinépítőknél, beállíthatjuk a végrehajtás kontextusát:

runBlocking (newSingleThreadContext ("dedikáltTéma")) {val eredmény = deferred.await () println (eredmény)}

Ne feledje, hogy létrehozhatunk egy új szálat, amelyben végrehajthatjuk a koroutint. A dedikált szál azonban drága erőforrás. És amikor már nincs rá szükség, akkor engedjük el, vagy még jobb, ha újra felhasználjuk az alkalmazás során.

4. Következtetés

Ebben az oktatóanyagban megtanultuk az aszinkron, nem blokkoló kód futtatását egy szál létrehozásával.

A szál alternatívájaként azt is láthattuk, hogy Kotlin hogyan alkalmazza a koroutinokat egyszerű és elegáns megközelítéssel.

Szokás szerint az ebben az oktatóanyagban bemutatott összes kódminta elérhető a Githubon.