Concurrency și corutine în Kotlin

Java Virtual Machine, sau JVM pe scurt, suportă multithreading. Orice proces pe care îl executați este liber să creați un număr rezonabil de fire pentru a efectua sarcini multiple asincron. Cu toate acestea, scrierea unui cod care poate face acest lucru într-un mod optim și fără erori poate fi extrem de greu. De-a lungul timpului, Java, alte limbi JVM și o mulțime de biblioteci terțe au încercat să vină cu abordări creative și elegante pentru a rezolva această problemă.

De exemplu, Java 5 a introdus framework-ul executiv, care vă permite să decuplați detaliile managementului firului de logica dvs. de afaceri. Java 8 oferă fluxuri paralele, care pot fi utilizate cu ușurință cu expresii lambda. RxJava aduce extensii reactive la Java, permițându-vă să scrieți cod asincron foarte concis și lizibil.

Kotlin susține aproape toate aceste abordări și oferă câteva din propria sa abordare. În acest tutorial, vă vom arăta cum le puteți utiliza în aplicațiile Android.

Cerințe preliminare

Pentru a putea urma acest tutorial, veți avea nevoie de:

  • Android Studio 2.3.3 sau o versiune ulterioară
  • Kotlin plugin 1.1.51 sau mai mare
  • o înțelegere de bază a firelor Java

Dacă nu sunteți confortabil să lucrați cu expresii lambda și interfețe SAM, vă recomand să citiți și următorul tutorial înainte de a continua:

De asemenea, puteți afla toate intrările și ieșirile din limba Kotlin în seria Kotlin From Scratch.

  • Kotlin de la zero: Nullabilitate, buclă și condiții

    Kotlin este gratuit și open source, și face codificarea Android chiar mai distractivă. În acest tutorial, vom analiza nullabilitatea, buclele și condițiile din Kotlin.
    Chike Mgbemena
    Kotlin
  • Kotlin de la zero: mai distractiv cu funcții

    Aflați despre funcțiile de nivel superior, expresiile lambda, funcțiile anonime, funcțiile locale, funcțiile infix și funcțiile membrilor din Kotlin.
    Chike Mgbemena
    Kotlin

1. Crearea de fire

De obicei, instanțe de clase care implementează runnable sunt folosite pentru a crea fire în Kotlin. Deoarece runnable interfața are doar o singură metodă, alerga() , puteți utiliza caracteristica de conversie SAM a lui Kotlin pentru a crea fire noi cu un cod minimal de boilerplate.

Iată cum puteți utiliza fir() funcția, care face parte din biblioteca standard a lui Kotlin, pentru a crea rapid și a începe un nou thread:

thread // unele operațiuni de lungă durată

Abordarea de mai sus este adecvată numai atunci când aveți nevoie să faceți ocazional un fir sau două. Dacă concurrency este o parte importantă a logicii de afaceri a aplicației dvs. și aveți nevoie de un număr mare de fire, utilizarea unei baze de thread cu un serviciu executor este o idee mai bună. 

De exemplu, următorul cod utilizează newFixedThreadPool () metodă a executori pentru a crea o piscină de fire conținând opt fire reutilizabile și care rulează un număr mare de operații de fundal pe ea:

val myService: ExecutorService = Executors.newFixedThreadPool (8) var i = 0 în timp ce (i < items.size)  // items may be a large array val item = items[i] myService.submit  processItem(item) // a long running operation  i += 1 

Ar putea să nu fie evident la prima vedere, dar, în codul de mai sus, argumentul pentru a depune() metoda serviciului executor este de fapt a runnable obiect.

2. Obținerea rezultatelor din subiecte

Sarcini de fundal create utilizând runnable interfața nu poate returna direct rezultatele. Dacă doriți să primiți rezultate din firele dvs., trebuie să utilizați nevărsat interfață, care este, de asemenea, o interfață SAM.

Când treceți a nevărsat obiecte față de a depune() metoda unui serviciu executor, primiți un Viitor obiect. După cum sugerează și numele, Viitor obiect va conține rezultatul nevărsat la un moment dat în viitor, când serviciul executorului a terminat să îl execute. Pentru a obține rezultatul real de la Viitor obiect, tot ce trebuie să faceți este să-i numiți obține() metodă - dar aveți grijă, firul dvs. va bloca dacă îl numiți prematură.

Următorul exemplu de cod vă arată cum să creați un nevărsat obiect care returnează a Viitor de tip Şir, rulați-l și tipăriți rezultatul:

val myService: ExecutorService = Executors.newFixedThreadPool (2) val rezultat = myService.submit (Callable // o operație de fundal care generează // un șir) // Alte operațiuni // Tipăriți rezultatul Log.d (TAG, result.get ())

3. Sincronizarea thread-urilor

Spre deosebire de Java, Kotlin nu are sincronizate cuvinte cheie. Prin urmare, pentru a sincroniza mai multe operațiuni de fundal, este de așteptat să utilizați fie @Synchronized adnotare sau sincronizate () funcția inline a bibliotecii. Adnotarea poate sincroniza o întreagă metodă și funcția funcționează pe un bloc de instrucțiuni.

// functie sincronizata @ distractie sincronizata myFunction ()  distractie myOtherFunction () // un bloc sincronizat sincronizat (aceasta) 

Amandoua @Synchronized adnotare și sincronizate () utilizați conceptul de încuietori ale monitorului. 

Dacă nu știți deja, fiecare obiect de pe JVM are un monitor asociat cu acesta. Deocamdată, vă puteți gândi la un monitor ca un semn special pe care un fir poate dobândi sau îl poate bloca pentru a obține acces exclusiv la obiect. Odată ce monitorul unui obiect este blocat, alte fire care vor să funcționeze pe obiect vor trebui să aștepte până când monitorul este eliberat sau deblocat din nou.

In timp ce @Synchronized adnotarea blochează monitorul obiectului la care aparține metoda asociată, sincronizate () funcția poate bloca monitorul oricărui obiect care ia fost transmis ca argument.

4. Înțelegerea corutinei

Printr-o bibliotecă experimentală, Kotlin oferă o abordare alternativă pentru a realiza concurrency: corutine. Corutinele sunt mult mai ușoare decât firele și sunt mult mai ușor de gestionat.

În aplicațiile mobile cu mai multe fire, firele sunt de obicei folosite pentru operații cum ar fi preluarea de informații de pe Internet sau interogarea bazelor de date. Astfel de operațiuni nu implică mult calcul, iar firele petrec cea mai mare parte a vieții într-o stare blocată, așteptând doar ca datele să vină de undeva în altă parte. După cum probabil puteți spune, nu este o modalitate foarte eficientă de a utiliza CPU-ul.

Corutinele sunt concepute pentru a fi utilizate în loc de fire pentru astfel de operațiuni. Cel mai important lucru pe care trebuie să-l înțelegeți despre corutine este că acestea sunt suspendabile. Cu alte cuvinte, în loc de blocare, ei se pot opri atunci când este necesar și continuă continuu mai târziu. Acest lucru duce la o utilizare mult mai bună a procesorului. Într-adevăr, cu corutine bine concepute, puteți executa cu ușurință zeci de operații de fundal.

Pentru a putea utiliza coroutine în proiectul dvs. Android Studio, asigurați-vă că adăugați următoarele compila dependența în aplicaţia ale modulului build.gradle fişier:

compilați org.jetbrains.kotlinx: kotlinx-coroutines-android: 0.19.3 '

5. Crearea funcțiilor de suspendare

O corutină poate fi suspendată numai cu ajutorul unei funcții de suspendare. Prin urmare, cele mai multe corutine au apeluri la cel puțin o astfel de funcție în interiorul lor.

Pentru a crea o funcție de suspendare, tot ce trebuie să faceți este să adăugați suspenda modificator la o funcție obișnuită. Iată o funcție tipică de suspendare care execută o cerere HTTP GET utilizând biblioteca khttp:

suspendați distracția fetchWebsiteContents (url: String): String returnați khttp.get (url) .text

Rețineți că o funcție de suspendare poate fi apelată numai printr-o funcție de corecție sau o altă funcție de suspendare. Dacă încercați să o apelați din orice altă parte, codul dvs. nu va reuși să compileze.

6. Crearea de corutine

Când vine vorba de crearea unui corutin nou, biblioteca standard a lui Kotlin are destule constructori de corutină care te fac să te simți rasfatată pentru alegere. Cel mai simplu constructor de corutină pe care îl puteți utiliza este lansa() funcția și, ca majoritatea celorlalți constructori de corutină, se așteaptă ca o lambda de suspendare, care nu este decât o funcție anonimă de suspendare. Ca atare, această lambda este ceea ce devine corutina.

Următorul cod creează o corutină care face două apeluri secvențiale la funcția de suspendare pe care am creat-o în etapa anterioară:

val job1 = lansare val site1 = fetchWebsiteContents ("https://code.tutsplus.com") val site2 = fetchWebsiteContents ("https://design.tutsplus.com")

Valoarea returnată a lansa() funcția este a Loc de munca obiect, pe care îl puteți folosi pentru a administra corutina. De exemplu, puteți să-i sunați a adera() metoda de a aștepta pentru completarea corutinei. În mod similar, îi puteți numi Anulare() metodă de anulare a corutinei.

Utilizarea lansa() funcția este asemănătoare cu crearea unui fir nou cu a runnable obiect, în primul rând pentru că nu puteți returna nici o valoare din acesta. Dacă doriți să puteți returna o valoare din corutină, trebuie să o creați utilizând async () funcția.

async () funcția returnează a amânat obiect, care, la fel ca și Loc de munca obiect, vă permite să gestionați corutina. Cu toate acestea, vă permite de asemenea să utilizați așteaptă () funcția de a aștepta rezultatul corutinei fără a bloca firul curent.

De exemplu, ia în considerare următoarele corutine care utilizează fetchWebsiteContents () suspendă funcția și returnează lungimile conținutului a două adrese de pagini web diferite:

val jobForLength1 = async fetchWebsiteContents ("https://webdesign.tutsplus.com") .length val jobForLength2 = async fetchWebsiteContents ("https://photography.tutsplus.com") .length

Cu codul de mai sus, ambele corutine vor începe imediat și se vor desfășura în paralel.

Dacă doriți acum să utilizați lungimile returnate, trebuie să apelați așteaptă () pe ambele amânat obiecte. Cu toate acestea, pentru că așteaptă () metoda este de asemenea o funcție de suspendare, trebuie să vă asigurați că o numiți dintr-o altă cotitură.

Următorul cod vă arată cum puteți calcula suma celor două lungimi folosind un nou corutan creat cu lansa() funcţie:

lansați val sum = jobForLength1.await () + jobForLength2.await () println ("Descărcați $ bytes sum!")

7. Utilizarea Coroutines în thread-ul UI

Corutinele folosesc firele de fundal pe plan intern, motiv pentru care nu rulează implicit pe firul de aplicație al aplicației Android. În consecință, dacă încercați să modificați conținutul interfeței de utilizator a aplicației din interiorul unei corutine, veți întâlni o eroare de rulare. Din fericire, este ușor să executați o corutină pe firul UI: trebuie doar să treceți UI obiect ca argument pentru constructorul dvs. de corutină.

De exemplu, iată cum puteți rescrie ultima corutină pentru a afișa suma din interiorul a TextView widget:

lansare (UI) val sum = jobForLength1.await () + jobForLength2.await () myTextView.text = "Descarcate suma $ bytes!" 

Codul de mai sus ar putea părea lumesc la început, dar uite din nou. Nu numai că este în stare să aștepte ca două operații de fundal să se finalizeze fără a utiliza callback-uri, dar poate face acest lucru pe firul aplicației UI, fără a-l bloca!

Având abilitatea de a aștepta firul UI, fără a vă face să vă simțiți lent sau să declanșați o eroare a aplicației care nu răspunde, deseori denumită ANR, simplifică o mulțime de sarcini altfel complexe. 

De exemplu, cu suspendarea întârziere() funcția, care este echivalentul non-blocant al lui Thread.sleep () , puteți crea acum animații cu bucle. Pentru a vă ajuta să începeți, iată un eșantion de corutină care mărește coordonata x a TextView widget la fiecare 400 ms, creând astfel un efect de tip marquee:

lansare (UI) în timp ce (myTextView.x < 800)  myTextView.x += 10 delay(400)  

Concluzie

În timp ce dezvoltați aplicații Android, este imperativ să efectuați operații de lungă durată în fire de fundal. În acest tutorial, ați învățat câteva abordări pe care le puteți urma pentru a crea și a gestiona astfel de fire în Kotlin. De asemenea, ați învățat cum să utilizați funcția de corutine experimentale încă pentru a aștepta în interiorul firelor, fără a le bloca.

Pentru a afla mai multe despre corutine, vă puteți referi la documentația oficială. Și în timp ce sunteți aici, verificați câteva dintre celelalte postări despre dezvoltarea companiei Kotlin și Android!

Cod