Tråder mot Coroutines i Kotlin

1. Introduksjon

I denne raske opplæringen skal vi lage og utføre tråder i Kotlin.

Senere vil vi diskutere hvordan du kan unngå det helt, til fordel for Kotlin Coroutines.

2. Lage tråder

Å lage en tråd i Kotlin ligner på å gjøre det i Java.

Vi kunne enten utvide Tråd klasse (selv om det ikke anbefales på grunn av at Kotlin ikke støtter flere arv):

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

Eller vi kan implementere Kjørbar grensesnitt:

klasse SimpleRunnable: Runnable {public override fun run () {println ("$ {Thread.currentThread ()} has run.")}}

Og på samme måte som vi gjør i Java, kan vi utføre det og kalle start() metode:

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

Alternativt, som Java 8, støtter Kotlin SAM-konverteringer, derfor kan vi dra nytte av det og sende en lambda:

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

2.2. Kotlin tråd() Funksjon

En annen måte er å vurdere funksjonen tråd() som Kotlin gir:

morsom tråd (start: boolsk = sant, erDemon: boolsk = falsk, contextClassLoader: ClassLoader? = null, navn: streng? = null, prioritet: Int = -1, blokk: () -> Enhet): tråd

Med denne funksjonen kan en tråd instantiseres og utføres ved å:

tråd (start = sann) {println ("$ {Thread.currentThread ()} har kjørt.")}

Funksjonen godtar fem parametere:

  • start - Å kjøre tråden umiddelbart
  • isDaemon - Å lage tråden som en demonstråd
  • contextClassLoader - En klasselaster til bruk for lasting av klasser og ressurser
  • Navn - For å angi navnet på tråden
  • prioritet - For å sette prioriteten til tråden

3. Kotlin Coroutines

Det er fristende å tenke at gyting av flere tråder kan hjelpe oss med å utføre flere oppgaver samtidig. Dessverre er det ikke alltid sant.

Å lage for mange tråder kan faktisk gjøre at et program underpresterer i noen situasjoner; tråder er gjenstander som pålegger overhead under tildeling av gjenstander og søppeloppsamling.

For å overvinne disse problemene, Kotlin introduserte en ny måte å skrive asynkron, ikke-blokkerende kode på; Coroutine.

I likhet med tråder kan coroutines løpe inn samtidig, vente på og kommunisere med hverandre med den forskjellen at det å lage dem er mye billigere enn tråder.

3.1. Coroutine Context

Før vi presenterer koroutbyggerne som Kotlin tilbyr utenom boksen, må vi diskutere Coroutine-konteksten.

Coroutines utfører alltid i en eller annen sammenheng som er et sett med forskjellige elementer.

Hovedelementene er:

  • Jobb - modellerer en kansellerbar arbeidsflyt med flere tilstander og en livssyklus som kulminerer med at den er fullført
  • Dispatcher - bestemmer hvilken tråd eller tråder den korresponderende koroutinen bruker for utførelsen. Med senderen, vi kan begrense koroutinekjøring til en bestemt tråd, sende den til et trådbasseng, eller la den kjøre ubegrenset

Vi får se hvordan vi spesifiserer konteksten mens vi beskriver coroutines i de neste trinnene.

3.2. lansering

De lansering funksjonen er en coroutine builder som starter en ny coroutine uten å blokkere den gjeldende tråden og returnerer en referanse til coroutine som en Jobb gjenstand:

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

Den har to valgfrie parametere:

  • kontekst - Konteksten der coroutine blir utført, hvis den ikke er definert, arver den konteksten fra CoroutineScope den lanseres fra
  • start - Startalternativene for coroutine. Som standard er coroutine umiddelbart planlagt for kjøring

Vær oppmerksom på at ovennevnte kode blir utført i en delt bakgrunn av tråder fordi vi har brukt Dispatchers. Standard som lanserer den i GlobalScope.

Alternativt kan vi bruke GlobalScope.lansering som bruker samme utsender:

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

Når vi bruker Dispatchers. Standard eller GlobalScope.lansering vi lager en coroutine på toppnivå. Selv om den er lett, bruker den fremdeles litt minneressurser mens den kjører.

I stedet for å lansere coroutines i GlobalScope, akkurat som vi vanligvis gjør med tråder (tråder er alltid globale), kan vi starte coroutines i det spesifikke omfanget av operasjonen vi utfører:

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

I dette tilfellet starter vi ny coroutine inne i runBlocking coroutine builder (som vi vil beskrive senere) uten å spesifisere konteksten. Dermed vil coroutine arve runBlocking’S sammenheng.

3.3. asynkronisering

En annen funksjon som Kotlin gir for å lage en coroutine er asynkronisering.

De asynkronisering funksjon oppretter en ny coroutine og returnerer et fremtidig resultat som en forekomst av Utsatt:

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

utsatt er en ikke-blokkerende kansellerbar fremtid som beskriver et objekt som fungerer som en proxy for et resultat som i utgangspunktet er ukjent.

Som lansering, vi kan spesifisere en kontekst der coroutine skal utføres, samt et startalternativ:

val utsatt = asynkronisering (Dispatchers.Unconfined, CoroutineStart.LAZY) {println ("$ {Thread.currentThread ()} har kjørt.")}

I dette tilfellet har vi lansert coroutine ved hjelp av Dispatchere.Ubegrenset som starter coroutines i innringertråden, men bare til det første suspensjonspunktet.

Noter det Dispatchere.Ubegrenset passer godt når en coroutine ikke bruker CPU-tid eller oppdaterer noen delte data.

I tillegg gir Kotlin Dispatchers.IO som bruker et felles utvalg av tråder som er opprettet etter behov:

val utsatt = async (Dispatchers.IO) {println ("$ {Thread.currentThread ()} har kjørt.")}

Dispatchers.IO anbefales når vi trenger å gjøre intensive I / O-operasjoner.

3.4. runBlocking

Vi hadde en tidligere titt på runBlocking, men la oss nå snakke om det nærmere.

runBlocking er en funksjon som kjører en ny coroutine og blokkerer den gjeldende tråden til den er fullført.

Som et eksempel i forrige utdrag lanserte vi coroutine, men vi ventet aldri på resultatet.

For å vente på resultatet, må vi ringe avvente() suspendere metoden:

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

avvente() er det som kalles en suspenderingsfunksjon. Suspend-funksjoner er kun tillatt å ringe fra en coroutine eller en annen suspendere-funksjon. Av denne grunn har vi lagt den inn i en runBlocking påkallelse.

Vi bruker runBlocking i hoved- funksjoner og i tester slik at vi kan koble blokkeringskode til andre skrevet i suspenderende stil.

På en lignende måte som vi gjorde i andre coroutine-byggere, kan vi sette kjøringskonteksten:

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

Merk at vi kan lage en ny tråd der vi kan utføre coroutine. En dedikert tråd er imidlertid en kostbar ressurs. Og når det ikke lenger er behov for det, bør vi slippe det eller enda bedre bruke det gjennom hele applikasjonen.

4. Konklusjon

I denne opplæringen lærte vi hvordan vi kan utføre asynkron, ikke-blokkerende kode ved å lage en tråd.

Som et alternativ til tråden har vi også sett hvordan Kotlins tilnærming til bruk av coroutines er enkel og elegant.

Som vanlig er alle kodeeksempler vist i denne opplæringen tilgjengelig på Github.


$config[zx-auto] not found$config[zx-overlay] not found