Tråde vs Coroutines i Kotlin

1. Introduktion

I denne hurtige vejledning skal vi oprette og udføre tråde i Kotlin.

Senere vil vi diskutere, hvordan man helt undgår det til fordel for Kotlin Coroutines.

2. Oprettelse af tråde

Oprettelse af en tråd i Kotlin svarer til at gøre det i Java.

Vi kunne enten udvide Tråd klasse (selvom det ikke anbefales på grund af Kotlin understøtter ikke flere arv):

klasse SimpleThread: Tråd () {public override fun run () {println ("$ {Thread.currentThread ()} har kørt.")}}

Eller vi kan implementere Kan køres grænseflade:

klasse SimpleRunnable: Kan køres {public override fun run () {println ("$ {Thread.currentThread ()} has run.")}}

Og på samme måde som vi gør i Java, kan vi udføre det ved at kalde Start() metode:

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

Alternativt, ligesom Java 8, understøtter Kotlin SAM-konverteringer, derfor kan vi drage fordel af det og videregive en lambda:

val thread = Thread {println ("$ {Thread.currentThread ()} has run.")} thread.start ()

2.2. Kotlin tråd() Fungere

En anden måde er at overveje funktionen tråd() at Kotlin giver:

sjov tråd (start: Boolsk = sand, isDaemon: Boolsk = falsk, contextClassLoader: ClassLoader? = null, navn: String? = null, prioritet: Int = -1, blok: () -> Enhed): Tråd

Med denne funktion kan en tråd instantieres og udføres simpelthen ved at:

tråd (start = sand) {println ("$ {Thread.currentThread ()} er kørt.")}

Funktionen accepterer fem parametre:

  • Start - At straks køre tråden
  • isDaemon - At oprette tråden som en dæmontråd
  • contextClassLoader - En klasselæsser til brug ved indlæsning af klasser og ressourcer
  • navn - For at indstille navnet på tråden
  • prioritet - For at indstille trådens prioritet

3. Kotlin Coroutines

Det er fristende at tænke, at gydning af flere tråde kan hjælpe os med at udføre flere opgaver samtidigt. Desværre er det ikke altid sandt.

Oprettelse af for mange tråde kan faktisk gøre en applikation dårligere præsteret i nogle situationer; tråde er objekter, der pålægger overhead under objektallokering og affaldsopsamling.

For at overvinde disse problemer, Kotlin introducerede en ny måde at skrive asynkron, ikke-blokerende kode på; Coroutine.

I lighed med tråde kan coroutines køre samtidigt, vente på og kommunikere med hinanden med den forskel, at oprettelse af dem er langt billigere end tråde.

3.1. Coroutine Kontekst

Før vi præsenterer de coroutine-bygherrer, som Kotlin leverer out-of-the-box, er vi nødt til at diskutere Coroutine Context.

Coroutines udfører altid i en eller anden sammenhæng, der er et sæt forskellige elementer.

Hovedelementerne er:

  • Job - modellerer en annullerbar arbejdsgang med flere tilstande og en livscyklus, der kulminerer med dens afslutning
  • Dispatcher - bestemmer hvilken tråd eller tråd den tilsvarende coroutine bruger til dens udførelse. Med afsenderen vi kan begrænse coroutine-udførelse til en bestemt tråd, afsende den til en trådpulje eller lade den køre ukonkurreret

Vi ser, hvordan vi specificerer konteksten, mens vi beskriver coroutines i de næste faser.

3.2. lancering

Det lancering funktionen er en coroutine builder, der starter en ny coroutine uden at blokere den aktuelle tråd og returnerer en henvisning til coroutine som en Job objekt:

runBlocking {val job = launch (Dispatchers.Default) {println ("$ {Thread.currentThread ()} er kørt.")}}

Den har to valgfri parametre:

  • sammenhæng - Den kontekst, hvor coroutinen udføres, hvis den ikke er defineret, arver den konteksten fra CoroutineScope den lanceres fra
  • Start - Startmulighederne for coroutine. Som standard er coroutine straks planlagt til udførelse

Bemærk, at ovenstående kode udføres i en delt baggrundspool af tråde, fordi vi har brugt Dispatchers. Standard der lancerer det i GlobalScope.

Alternativt kan vi bruge GlobalScope.lunch der bruger den samme afsender:

val job = GlobalScope.launch {println ("$ {Thread.currentThread ()} er kørt.")}

Når vi bruger Dispatchers. Standard eller GlobalScope.lunch vi skaber en top-coroutine. Selvom det er let, bruger det stadig nogle hukommelsesressourcer, mens det kører.

I stedet for at lancere coroutines i GlobalScope, ligesom vi normalt gør med tråde (tråde er altid globale), kan vi starte coroutines i det specifikke omfang af den operation, vi udfører:

runBlocking {val job = launch {println ("$ {Thread.currentThread ()} er kørt.")}}

I dette tilfælde starter vi ny coroutine inde i runBlocking coroutine builder (som vi vil beskrive senere) uden at specificere konteksten. Således vil coroutine arve runBlocking'S sammenhæng.

3.3. asynkronisering

En anden funktion, som Kotlin giver for at skabe en coroutine, er asynkronisering.

Det asynkronisering funktion opretter en ny coroutine og returnerer et fremtidigt resultat som en forekomst af Udskudt:

val deferred = async {[email protected] "$ {Thread.currentThread ()} er kørt." }

udsat er en ikke-blokerende annullerbar fremtid, der beskriver et objekt, der fungerer som en proxy for et resultat, der oprindeligt er ukendt.

Synes godt om lancering, Vi kan specificere en kontekst, hvor coroutine skal udføres, samt en startmulighed:

val udskudt = async (Dispatchers.Unconfined, CoroutineStart.LAZY) {println ("$ {Thread.currentThread ()} er kørt.")}

I dette tilfælde har vi lanceret coroutine ved hjælp af Afsendere.Ubegrænset som starter coroutines i opkaldstråden, men kun indtil det første suspensionspunkt.

Noter det Afsendere.Ubegrænset passer godt, når en coroutine ikke bruger CPU-tid eller opdaterer delte data.

Derudover leverer Kotlin Dispatchers.IO der bruger en delt pulje af on-demand-oprettede tråde:

val udskudt = async (Dispatchers.IO) {println ("$ {Thread.currentThread ()} er kørt.")}

Dispatchers.IO anbefales, når vi har brug for intensive I / O-operationer.

3.4. runBlocking

Vi kiggede tidligere på runBlocking, men lad os nu tale mere om det.

runBlocking er en funktion, der kører en ny coroutine og blokerer den aktuelle tråd indtil dens afslutning.

Som eksempel i det forrige uddrag lancerede vi coroutine, men vi ventede aldrig på resultatet.

For at vente på resultatet er vi nødt til at ringe til vente() suspendere metode:

// async-kode går her runBlocking {val result = deferred.await () println (result)}

vente() er det, der kaldes en suspendefunktion. Suspendefunktioner må kun kaldes fra en coroutine eller en anden suspendefunktion. Af denne grund har vi lukket det i en runBlocking påkaldelse.

Vi bruger runBlocking i vigtigste funktioner og i test, så vi kan linke blokerende kode til andre skrevet i suspenderende stil.

På en lignende måde som vi gjorde i andre coroutine bygherrer, kan vi indstille eksekveringskonteksten:

runBlocking (newSingleThreadContext ("dedicatedThread")) {val result = deferred.await () println (result)}

Bemærk, at vi kan oprette en ny tråd, hvor vi kunne udføre coroutine. En dedikeret tråd er dog en dyr ressource. Og når det ikke længere er nødvendigt, skal vi frigive det eller endnu bedre genbruge det i hele applikationen.

4. Konklusion

I denne vejledning lærte vi, hvordan man udfører asynkron, ikke-blokerende kode ved at oprette en tråd.

Som et alternativ til tråden har vi også set, hvordan Kotlins tilgang til brug af coroutines er enkel og elegant.

Som sædvanligt er alle kodeeksempler vist i denne vejledning tilgængelige på Github.