1 of 59

Tehnici avansate pentru

dezvoltarea aplicațiilor mobile

7 - Kotlin - Corutine

2 of 59

Cod sincron

  • O singură operație este rulată la un moment dat

  • Operațiile sunt executate în ordine, în mod secvențial

  • O operație trebuie să se termine înainte ca următoarea să înceapă

7 - Kotlin - Corutine

2

3 of 59

Corutine

  • co = cooperant
  • rutina = funcție

  • Codul cooperează pentru a partaja bucla de evenimente
    • se suspendă pentru a aștepta ceva
    • dă șansa altor operații să fie executate

  • Ex: apelăm delay()
    • corutina este suspendată
    • pot fi rulate alte operații în timpul acesta

7 - Kotlin - Corutine

3

4 of 59

Avantajele corutinelor

  • Mai lightweight decât thread-urile
    • nu necesită resurse suplimentare din SO

  • Oferă concurență structurată
    • putem implementa ușor operații asincrone

  • Suspendarea și reluarea execuției
    • operații lungi fără să blocheze thread-ul principal

  • Gestiunea excepțiilor

4

5 of 59

Funcții din biblioteca corutinelor

  • runBlocking() rulează expresia lambda într-o corutină
    • este sincronă => se va întoarce doar după ce toate operațiile s-au terminat

  • delay() suspendă execuția corutinei o perioadă
    • este o funcție suspendabilă
    • poate fi rulată doar dintr-o corutină sau din altă funcție suspendabilă

7 - Kotlin - Corutine

5

6 of 59

Funcții suspendabile

  • Funcții care pot fi suspendate și continuate mai târziu

  • Punct de suspendare
    • locul din cod unde execuția poate fi suspendată

  • O funcție suspendabilă poate avea zero sau mai multe puncte de suspendare

  • Cuvântul cheie suspend înaintea funcției

7 - Kotlin - Corutine

6

7 of 59

Funcții suspendabile - exemplu

7 - Kotlin - Corutine

7

8 of 59

Funcții suspendabile - exemplu

8

9 of 59

Funcții suspendabile - exemplu

  • De ce avem nevoie de runBlocking()?
    • Codul oricum ar fi fost secvențial

  • R: funcțiile suspendabile pot fi rulate:
    • dintr-o corutină (ex. runBlocking)
    • dintr-o funcție suspendabilă

7 - Kotlin - Corutine

9

10 of 59

Cod asincron

10

11 of 59

Concurență structurată

  • Corutinele Kotlin - concurență structurată

  • Codul este secvențial implicit și cooperează cu o buclă de evenimente

  • Când o funcție se întoarce
    • => și-a terminat complet munca

7 - Kotlin - Corutine

11

12 of 59

Cod asincron

  • O operație nu trebuie să se termine pentru ca alta să înceapă

  • Putem rula mai multe operații în paralel

  • Corutinele permit scrierea codului asincron

7 - Kotlin - Corutine

12

13 of 59

Cod asincron - launch()

  • launch() lansează o corutină nouă

  • Permite rularea asincron, în paralel
    • mai multe operații lansate cu launch()

  • Se întoarce chiar dacă operația nu a fost terminată
    • => permite lansarea altor operații

7 - Kotlin - Corutine

13

14 of 59

launch() - exemplu

  • se execută în paralel
  • timp de execuție mai mic

14

15 of 59

Cod asincron - launch()

  • launch() nu întoarce o valoare

  • Nu putem ști când s-a terminat o operație lansată cu launch()

7 - Kotlin - Corutine

15

16 of 59

Cod asincron - async()

  • Asincronă, similară cu launch()

  • Funcția async()
    • vrem să știm când s-a terminat operația
    • avem nevoie să întoarcă o valoare

  • Întoarce un obiect de tipul Deferred
    • o promisiune că va întoarce un rezultat
    • rezultatul obținut cu funcția await()

7 - Kotlin - Corutine

16

17 of 59

async() - exemplu

  • 2 corutine care rulează în paralel

  • Când ambele se termină obținem valorile returnate

17

18 of 59

Descompunerea paralelă

  • Împărțirea unei probleme în mai multe subtask-uri
    • => pot fi rezolvate în paralel

  • Rezultatele subtask-urilor sunt gata
    • => se pot combina în rezultatul final

7 - Kotlin - Corutine

18

19 of 59

Descompunerea paralelă

  • coroutineScope - creează un domeniu local
    • grupează 2 corutine rulate în paralel
    • obținem, combinăm și returnăm rezultatele
    • contează pentru anulare și excepții

7 - Kotlin - Corutine

19

20 of 59

Descompunerea paralelă

  • coroutineScope se va întoarce după ce corutinele incluse s-au terminat
    • Pare sincronă pentru apelant
    • Apelantul nu trebuie să gestioneze execuția în paralel

7 - Kotlin - Corutine

20

21 of 59

Concurență structurată

  • Operații concurente puse într-o singură operație sincronă
    • apelantul nu va ști că se realizează operații în paralel
    • apelantul trebuie să fie o funcție suspendabilă sau o corutină

7 - Kotlin - Corutine

21

22 of 59

Excepții și anulare

22

23 of 59

Ierarhie de corutine

  • Corutinele au relații părinte-copil

  • O corutină părinte va lansa o corutină copil
    • => o întreagă ierarhie de corutine

  • Cele două corutine din exemplul anterior = copii ai aceleiași corutine părinte

7 - Kotlin - Corutine

23

24 of 59

Excepții în corutine

  • Dacă o corutină copil aruncă o excepție
    • => corutina părinte va fi anulată
    • => corutinele copii ale acelui părinte vor fi anulate

  • Excepția este propagată în sus și în final programul se termină cu AssertionError

  • Trebuie să prindem și să gestionăm excepțiile

7 - Kotlin - Corutine

24

25 of 59

Gestiunea excepțiilor

  • Excepțiile pot fi prinse și gestionate cu blocuri try-catch
    • Previne terminarea bruscă a programului
  • Putem folosi try-catch și în corutine

7 - Kotlin - Corutine

25

26 of 59

Gestiunea excepțiilor - exemplu

7 - Kotlin - Corutine

26

27 of 59

Gestiunea excepțiilor - exemplu

  • Excepția din getTemperature este prinsă
    • În blocul catch:
      • Este printat un mesaj
      • Este returnată o valoare: “No temperature found”

  • Excepția nu este propagată la corutina părinte
    • nu afectează celelalte corutine copii
    • valoarea returnată de getForecast este afișată

7 - Kotlin - Corutine

27

28 of 59

Anularea corutinelor

  • Se face cu apelul cancel()
  • Nu va afecta corutina părinte sau celelalte corutine din același domeniu

7 - Kotlin - Corutine

28

29 of 59

Operații în execuția corutinei

  • 4 operații: lansarea, finalizarea, anularea, eșuarea

  • Lansarea unei corutine într-un domeniu care îi controlează durata vieții

  • Terminarea unei corutine când se termină copiii ei

7 - Kotlin - Corutine

29

30 of 59

Operații în execuția corutinei

  • Anularea se propagă în jos, la corutinele copii

  • Eșuarea cu excepție se propagă în sus
    • corutinele părinți și restul copiilor sunt anulate
    • până când excepția este prinsă și gestionată

7 - Kotlin - Corutine

30

31 of 59

Job-uri

31

32 of 59

Job

  • Lansarea unei corutine cu launch va returna o instanță Job

  • Se poate gestiona ciclul de viață al corutinei prin Job
    • Ex: se poate anula

7 - Kotlin - Corutine

32

33 of 59

Job

  • Se poate verifica dacă un Job este activ, anulat sau finalizat

  • Un Job este finalizat dacă:
    • corutina s-a terminat
    • a fost anulată
    • a eșuat cu excepție

7 - Kotlin - Corutine

33

34 of 59

Ierarhia de Job-uri

  • Job-urile urmăresc relația părinte-copil între corutine
  • Corutina lansează altă corutină
    • => Job părinte și Job copil

7 - Kotlin - Corutine

34

35 of 59

Ierarhie de Job-uri

  • Se formează o ierarhie de Job-uri

7 - Kotlin - Corutine

35

36 of 59

Ierarhia de Job-uri

  • Anularea unui Job părinte anulează și Job-urile copil

  • Anularea unui Job copil (cancel()) nu anulează părintele

  • Eșuarea unui Job anulează părintele cu aceeași excepție
    • propagând excepția în sus
    • până programul se termină cu eroare

7 - Kotlin - Corutine

36

37 of 59

Domeniu și context

37

38 of 59

CoroutineScope

  • Corutinele sunt lansate în domeniul CoroutineScope
    • Astfel nu avem corutine care nu sunt gestionate

  • launch() și async() sunt funcții extensie a CoroutineScope
    • crează corutine în acel domeniu

7 - Kotlin - Corutine

38

39 of 59

CoroutineScope

  • CoroutineScope
    • ciclu de viață
    • dictează cât “trăiesc” corutinele din interior

  • Domeniu anulat => job anulat => job-uri copii anulate

  • Job copil eșuat cu excepție => părinte și copii anulați
    • excepția aruncată către apelant

7 - Kotlin - Corutine

39

40 of 59

CoroutineContext

  • Interfața CoroutineScope include variabila CoroutineContext

  • launch/async crează o nouă corutină în CoroutineScope
    • moștenește contextul din domeniu

7 - Kotlin - Corutine

40

41 of 59

CoroutineContext

  • Informații despre contextul în care va rula corutina

  • O structură map cu elemente cheie - valoare
    • name = numele unic al corutinei
    • job = job-ul care controlează ciclul de viață al corutinei
    • dispatcher = asociază munca cu un anumit thread
    • exception handler = gestionează excepțiile aruncate de codul corutinei

7 - Kotlin - Corutine

41

42 of 59

CoroutineContext

  • Valori implicite:
    • name = "coroutine"
    • job = niciun Job părinte
    • dispatcher = Dispatchers.Default
    • exception handler = niciun handler

  • Definirea unui context:

7 - Kotlin - Corutine

42

43 of 59

CoroutineContext

  • Lansăm o nouă corutină => va moșteni contextul părintelui
    • Doar Job-ul este înlocuit cu cel nou

  • Se poate suprascrie orice element din context
    • Argument la launch/async

7 - Kotlin - Corutine

43

44 of 59

Dispatcher

44

45 of 59

Thread-uri

  • Aplicațiile Android au un main thread (UI thread)
    • evenimentele sistemului de operare
    • desenarea UI-ului
    • evenimentele UI

  • Majoritatea codului rulează pe main thread

7 - Kotlin - Corutine

45

46 of 59

Thread-uri

  • Codul blocant
    • va bloca thread-ul până la terminare
    • nu se poate rula altceva între timp

  • Codul non-blocant
    • va ceda thread-ul curent până este îndeplinită o condiție => se poate rula altceva
    • funcții asincrone

7 - Kotlin - Corutine

46

47 of 59

Recomandari legate de main thread

  • Dacă executăm operații lungi blocante pe main thread
    • ecranul nu se va mai actualiza la fel de des
    • tranziții bruste (jank)
    • ecranul nu mai răspunde la input

7 - Kotlin - Corutine

47

48 of 59

Recomandari legate de main thread

  • Operațiile blocante de pe main thread = foarte scurte
    • liber pentru a prelua evenimentele UI

  • Operațiile lungi trebuie mutate pe alte thread-uri
    • worker threads

7 - Kotlin - Corutine

48

49 of 59

Dispatcher

  • Asociază munca (corutina) cu un anumit thread
    • main thread
    • worker thread

  • Dispatchers în Kotlin:
    • Dispatchers.Main
    • Dispatchers.IO
    • Dispatchers.Default

49

50 of 59

Dispatchers în Kotlin

  • Dispatchers.Main
    • pentru a rula o corutină pe main thread
    • gestiunea interacțiunii și actualizărilor UI
    • operații scurte

7 - Kotlin - Corutine

50

51 of 59

Dispatchers în Kotlin

  • Dispatchers.IO
    • optimizat pentru operații I/O cu discul sau rețeaua
    • înafara main thread
    • ex: scriere/citire în fișiere
    • ex: comunicație în rețea

7 - Kotlin - Corutine

51

52 of 59

Dispatchers în Kotlin

  • Dispatchers.Default
    • implicit când folosim launch/async
      • doar dacă nu moștenește un dispatcher de la părinte
    • operații intensive computațional
    • înafara main thread
    • ex: procesarea unor imagini

7 - Kotlin - Corutine

52

53 of 59

Modificarea Dispatcher-ului

  • Folosind withContext()

7 - Kotlin - Corutine

53

54 of 59

Modificarea dispatcher-ului

  • Corutina este pornită pe main thread
    • Corutina moștenește contextul părintelui
    • Părintele folosește Dispatchers.Main

  • Supracriem cu Dispatchers.Default

  • Codul va fi executat de un worker thread în loc de main thread

7 - Kotlin - Corutine

54

55 of 59

Modificarea dispatcher-ului

  • Corutine pe main thread => le mutăm pe worker thread
    • folosim withContext pentru a schimba dispatcher-ul

  • În funcție de operație, alegem dispatcher-ul
    • Main, IO, Default
    • alege un thread potrivit din pool-ul de thread-uri

7 - Kotlin - Corutine

55

56 of 59

Modificarea dispatcher-ului

  • Bibliotecile de genul Retrofit și Room folosesc deja dispatcher-ul potrivit (ex. IO)
    • nu e nevoie de schimbarea manuală
    • se ocupă automat de schimbarea dispatcher-ului

  • Funcțiile suspendabile din bibliotecă sunt main-safe
      • => pot fi apelate dintr-o corutină ce rulează pe main thread

7 - Kotlin - Corutine

56

57 of 59

Bibliografie

7 - Kotlin - Corutine

57

58 of 59

Bibliografie

7 - Kotlin - Corutine

58

59 of 59

Cuvinte cheie

  • Cod sincron/asincron
  • Corutine
  • Funcții suspendabile
  • Concurență structurată
  • Launch/async
  • Descompunere paralelă
  • Ierarhie de corutine
  • Excepție
  • Anulare
  • Job
  • Scope
  • Context
  • Dispatcher

7 - Kotlin - Corutine

59

59