Niti protiv Coroutines u Kotlinu

1. Uvod

U ovom brzom uputstvu stvorit ćemo i izvršiti niti u Kotlinu.

Kasnije ćemo razgovarati o tome kako to uopće izbjeći, u korist Kotlin Coroutines.

2. Stvaranje niti

Stvaranje niti u Kotlinu slično je stvaranju niti u Javi.

Mogli bismo ili produžiti Nit klasa (iako se ne preporučuje zbog Kotlina ne podržava višestruko nasljeđivanje):

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

Ili možemo primijeniti Izvodljivo sučelje:

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

I na isti način kao što to radimo u Javi, možemo je izvršiti pozivajući početak() metoda:

val nit = SimpleThread () thread.start () val threadWithRunnable = Tema (SimpleRunnable ()) threadWithRunnable.start ()

Alternativno, poput Java 8, Kotlin podržava SAM pretvorbe, stoga ga možemo iskoristiti i proslijediti lambda:

val nit = Nit {println ("$ {Thread.currentThread ()} je pokrenut.")} thread.start ()

2.2. Kotlin nit() Funkcija

Drugi način je razmatranje funkcije nit() koje Kotlin pruža:

zabavna nit (početak: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, name: String? = null, prioritet: Int = -1, block: () -> Unit): Thread

Pomoću ove funkcije nit se može instancirati i izvršiti jednostavno:

nit (start = true) {println ("$ {Thread.currentThread ()} je pokrenut.")}

Funkcija prihvaća pet parametara:

  • početak - Da biste odmah pokrenuli nit
  • isDaemon - Da biste stvorili nit kao demon temu
  • contextClassLoader - Učitavač klasa koji se koristi za učitavanje klasa i resursa
  • Ime - Da biste postavili ime niti
  • prioritet - za postavljanje prioriteta niti

3. Kotlin koroutine

Primamljivo je pomisliti da nam mriještenje više niti može pomoći da istodobno izvršavamo više zadataka. Nažalost, to nije uvijek istina.

Stvaranje previše niti zapravo može u nekim situacijama učiniti aplikaciju slabijom; niti su objekti koji nameću opće troškove tijekom raspodjele objekata i prikupljanja smeća.

Da biste prevladali ove probleme, Kotlin je predstavio novi način pisanja asinkronog neblokirajućeg koda; Koroutina.

Slično nitima, koprogrami se mogu istodobno pokretati, čekati i međusobno komunicirati s tom razlikom što je njihovo stvaranje puno jeftinije od niti.

3.1. Koroinski kontekst

Prije nego što predstavimo graditelje koroutine koje Kotlin nudi izravno, moramo razgovarati o kontekstu koroutine.

Programi se uvijek izvršavaju u nekom kontekstu koji je skup različitih elemenata.

Glavni elementi su:

  • Posao - modelira tok rada koji se može otkazati s više stanja i životnim ciklusom koji kulminira njegovim završetkom
  • Dispečer - određuje koju nit ili niti odgovarajući program koristi za svoje izvršavanje. S dispečerom, možemo ograničiti izvršavanje podprograma na određenu nit, poslati je u spremište niti ili pustiti da radi neograničeno

Vidjet ćemo kako odrediti kontekst dok ćemo opisivati ​​podprograme u sljedećim fazama.

3.2. lansiranje

The lansiranje funkcija je graditelj koroutina koji pokreće novu koroutinu bez blokiranja trenutne niti i vraća referencu na podprogram kao a Posao objekt:

runBlocking {val posao = pokretanje (Dispatchers.Default) {println ("$ {Thread.currentThread ()} je pokrenut.")}}

Ima dva neobavezna parametra:

  • kontekst - Kontekst u kojem se koprogram izvršava, ako nije definiran, nasljeđuje ga iz CoroutineScope pokreće se iz
  • početak - Početne opcije za podprogram. Prema zadanim postavkama, koprogram je odmah zakazan za izvršenje

Imajte na umu da se gornji kôd izvršava u zajedničkom pozadinskom nizu niti jer smo koristili Otpremnici.Zadani koja ga lansira u GlobalScope.

Alternativno, možemo koristiti GlobalScope.launch koji koristi istog dispečera:

val posao = GlobalScope.launch {println ("$ {Thread.currentThread ()} je pokrenut.")}

Kad koristimo Otpremnici.Zadani ili GlobalScope.launch kreiramo vrhunsku koroutinu. Iako je lagan, i dalje troši neke resurse memorije dok radi.

Umjesto pokretanja podprograma u GlobalScopeu, baš kao što to obično radimo s nitima (niti su uvijek globalne), podprograme možemo pokrenuti u određenom opsegu operacije koju izvodimo:

runBlocking {val posao = pokretanje {println ("$ {Thread.currentThread ()} je pokrenut.")}}

U ovom slučaju započinjemo s novom koroutinom unutar runBlocking graditelj koroutina (što ćemo opisati kasnije) bez navođenja konteksta. Dakle, koroutina će naslijediti runBlockingKontekst.

3.3. asinkronizacija

Još jedna funkcija koju Kotlin pruža za stvaranje koroutine je asinkronizacija.

The asinkronizacija funkcija stvara novu koprogram i vraća budući rezultat kao instancu Odgođena:

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

Odgođena je budućnost koja se ne može blokirati i koja opisuje objekt koji djeluje kao proxy za rezultat koji je u početku nepoznat.

Kao lansiranje, možemo odrediti kontekst u kojem ćemo izvršiti podprogram kao i opciju pokretanja:

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

U ovom smo slučaju pokrenuli koroutinu pomoću Dispečeri.Neograničeno koji započinje programe u niti pozivatelja, ali samo do prve točke ovjesa.

Imajte na umu da Dispečeri.Neograničeno dobro se uklapa kada program ne troši procesorsko vrijeme niti ažurira dijeljene podatke.

Uz to Kotlin pruža Dispečeri.IO koji koristi zajednički bazen niti stvorenih na zahtjev:

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

Dispečeri.IO preporučuje se kada trebamo raditi intenzivne I / O operacije.

3.4. runBlocking

Imali smo raniji pogled runBlocking, ali sada razgovarajmo o tome dublje.

runBlocking je funkcija koja pokreće novu koprogram i blokira trenutnu nit do njenog završetka.

Kao primjer u prethodnom isječku, pokrenuli smo koroutinu, ali nikada nismo čekali rezultat.

Da bismo čekali rezultat, moramo nazvati čekati() suspendirana metoda:

// async kôd ide ovdje runBlocking {val rezultat = deferred.await () println (rezultat)}

čekati() je ono što se naziva suspend funkcija. Funkcije obustave smiju se pozivati ​​samo iz koprograma ili neke druge funkcije obustave. Iz tog smo razloga zatvorili u runBlocking prizivanje.

Koristimo runBlocking u glavni funkcije i u testovima, tako da blokirni kod možemo povezati s drugim napisanim u suspendirajućem stilu.

Na sličan način kao što smo to činili kod drugih graditelja koroutina, možemo postaviti kontekst izvršenja:

runBlocking (newSingleThreadContext ("namenjenaThread")) {val rezultat = deferred.await () println (rezultat)}

Imajte na umu da možemo stvoriti novu nit u kojoj bismo mogli izvršiti podprogram. Međutim, namjenska nit skup je resurs. A, kad više nije potreban, trebali bismo ga otpustiti ili još bolje ponovno upotrijebiti tijekom aplikacije.

4. Zaključak

U ovom smo tutorijalu naučili kako izvršavati asinkroni neblokirajući kôd stvaranjem niti.

Kao alternativu niti, vidjeli smo i kako je Kotlinov pristup korištenju koroutina jednostavan i elegantan.

Kao i obično, svi uzorci koda prikazani u ovom vodiču dostupni su na Githubu.