Coroutines — concepte cheie, Job și Dispatchers în Kotlin

Autor: IT Sectr Publicat: 2026-03-16 Timp de citire: 8 min

Coroutines (corutinele) sunt fire ușoare Kotlin pentru programare asincronă, disponibile prin biblioteca kotlinx.coroutines. Conform JetBrains Kotlin Documentation, 2026, Coroutines permit suspendarea executării unei funcții fără a bloca firul, spre deosebire de Thread-urile tradiționale. Corutinele rulează pe un pool limitat de fire, ceea ce le face de mii de ori mai ușoare decât firele native. Kotlin Coroutines sunt complet integrate cu Android Jetpack, Retrofit, Room și alte biblioteci populare din ecosistemul Android.

Puncte cheie

  • Coroutines — fire ușoare Kotlin pentru cod asincron fără blocare
  • Funcția suspend — funcție capabilă să se suspende și să se reia fără a bloca firul
  • Dispatcher definește pool-ul de fire pentru executarea corutinei
  • Job — descriptor al corutinei cu suport pentru anulare și urmărire a stării
  • CoroutineScope gestionează ciclul de viață al corutinelor și anularea lor la finalizare

Ce sunt corutinele Kotlin

Coroutines (corutinele) sunt un mecanism de programare asincronă în Kotlin, implementat în biblioteca kotlinx.coroutines. Spre deosebire de firele sistemului de operare, corutinele nu sunt legate de un fir specific: se pot suspenda pe un fir și relua pe altul. Un singur fir poate executa mii de corutine, comutând între ele cu un cost minim.

Corutinele au apărut în Kotlin 1.3 (2018) ca funcție experimentală și au devenit stabile în Kotlin 1.5 (2021). Coroutines rezolvă problema callback hell similar cu async/await, dar oferă un API mai bogat: canale (Channel), Flow, gestionarea excepțiilor în ierarhia Job și integrare directă cu Android Lifecycle.

Conform JetBrains (2025), fiecare corutină consumă aproximativ 100 de octeți de memorie față de 1+ MB pentru un fir nativ. Acest lucru permite rularea a milioane de corutine într-o singură aplicație fără riscul OutOfMemoryError. Exact ușurința corutinelor le face instrumentul preferat pentru asincronism în Android.

Cum funcționează corutinele sub capotă

Fiecare corutină Kotlin este compilată într-o mașină de stări prin Continuation Passing Style (CPS). Compilatorul adaugă un parametru ascuns Continuation la fiecare funcție suspend. Continuation conține punctul de reluare și toate variabilele locale. Când corutina se suspendă, runtime salvează Continuation, iar când se reia, îl restaurează pe orice fir disponibil din pool-ul Dispatcher.

Funcțiile suspend: suspendare și reluare

suspend — cuvântul cheie Kotlin care marchează o funcție ca fiind suspendabilă. O astfel de funcție poate fi apelată doar dintr-o altă funcție suspend sau dintr-o corutină. În interiorul unei funcții suspend pot fi apelate alte funcții suspend în orice ordine, iar fiecare punct de apel este un potențial punct de suspendare.

Mecanica este simplă: când o funcție suspendapează o altă funcție suspend, se suspendă în acel punct, eliberând firul. După finalizarea funcției apelate, runtime continuă execuția din locul salvat. Aceasta se numește cooperative cancellation — niciun fir nu este blocat.

  • Suspendare — corutina eliberează firul fără a-l bloca
  • Reluare — corutina continuă din același loc unde s-a suspendat
  • Fir — corutina se poate suspenda pe firul A și relua pe firul B
  • Excepții — gestionate prin try/catch la fel ca în codul sincron

Important: funcția suspend nu este asincronă implicit. Ordinea de execuție rămâne secvențială dacă nu se folosesc launch sau async. suspend permite doar ca funcția să fie suspendată fără a bloca firul și să facă parte din contextul corutinei. Continuation Passing Style este un model de compilare în care fiecare funcție suspend primește un callback ascuns Continuation, iar compilatorul generează o mașină de stări pentru gestionarea suspendărilor și reluărilor.

CoroutineScope și Structured Concurrency

CoroutineScope este un context care definește ciclul de viață al corutinelor. Toate corutinele trebuie lansate în interiorul unui scope. Când scope este anulat (de exemplu, la finalizarea Activity), toate corutinele sale copil sunt anulate automat. Acest lucru previne scurgerile de sarcini în fundal. Android Jetpack oferă scope-uri gata făcute pentru fiecare componentă: viewModelScope pentru ViewModel și lifecycleScope pentru Activity și Fragment, care se anulează automat la distrugerea componentei respective.

Structured Concurrency — principiul care garantează că o corutină nu se finalizează până când toate corutinele sale copil nu s-au finalizat. Ierarhia Job formează un arbore: corutina rădăcină creează un parent job, copiii creează child jobs. Anularea parent job se propagă la toți copiii. Structured Concurrency este diferența fundamentală între corutine și fire.

ScopeUnde se foloseșteAnulare
GlobalScopeDoar pentru sarcini daemonNu se anulează automat
viewModelScopeAndroid ViewModelLa curățarea ViewModel
lifecycleScopeAndroid Activity/FragmentLa distrugerea lifecycle
coroutineScopeÎn interiorul funcției suspendLa anularea job-ului părinte

SupervisorJob pentru gestionarea erorilor

Job-ul obișnuit la eroarea unei corutine copil anulează toți siblings. SupervisorJob este o excepție: defecțiunea unei corutine copil nu afectează restul. Acest lucru este important când mai multe sarcini independente rulează în paralel și una poate eșua fără a fi nevoie să le anuleze pe celelalte.

Dispatchers și constructorii de corutine

Dispatchers determină pe ce fire se execută corutinele. Dispatchers.Main — firul principal UI al Android. Dispatchers.IO — pool pentru operații blocante (rețea, disc). Dispatchers.Default — pentru sarcini CPU-intensive. Dispatchers.Unconfined — pornește în firul curent, dar nu garantează fixarea pe acesta. Alegerea Dispatcher-ului potrivit este critică pentru performanță: o sarcină IO pe Default va bloca pool-ul pentru calcule, iar o sarcină CPU pe IO va crea fire suplimentare.

withContext — funcție pentru comutarea Dispatcher-ului în interiorul corutinei. De exemplu, o funcție suspend care parsează JSON poate comuta pe Dispatchers.Default în timpul calculului și reveni pe Dispatchers.Main pentru actualizarea UI. withContext este cel mai folosit constructor în dezvoltarea Android.

Trei constructori principali de corutine

launch — lansează o corutină, returnează Job, nu returnează rezultat (fire-and-forget). async — lansează o corutină, returnează Deferred din care se poate obține rezultatul prin await. runBlocking — blochează firul curent pentru executarea corutinei (doar pentru teste și funcții main). Alegerea constructorului depinde de scenariu: launch se potrivește pentru evenimente și actualizări, async — pentru sarcini cu rezultat, runBlocking — doar pentru teste sau puncte de intrare.

Exemple de cod cu corutine în Kotlin

Să analizăm trei scenarii practice: corutina de bază cu launch, apelul paralel cu async și gestionarea erorilor cu SupervisorJob.

Lansarea corutinei cu launch

viewModelScope.launch lansează corutina în contextul ViewModel. La curățarea ViewModel, corutina este anulată automat.

kotlin
class ProfileViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch(Dispatchers.IO) {
            val user = api.fetchUser()
            withContext(Dispatchers.Main) {
                showUser(user)
            }
        }
    }
}

Cereri paralele cu async

coroutineScope cu async lansează trei cereri în paralel. Rezultatele sunt colectate prin .await(). Dacă oricare dintre cereri eșuează, toate sunt anulate.

kotlin
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user = async { api.fetchUser() }
    val posts = async { api.fetchPosts() }
    val stats = async { api.fetchStats() }
    Dashboard(user.await(), posts.await(), stats.await())
}

Gestionarea erorilor cu SupervisorJob

SupervisorJob permite fiecărei corutine să se finalizeze independent. O eroare într-o cerere nu le anulează pe celelalte.

kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
    try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
    try { api.fetchPosts() } catch (e: Exception) { log(e) }
}

Corutine vs fire: comparație și scenarii

Firele (Thread) — primitive ale sistemului de operare. Fiecare fir are propriul stack (~1 MB) și necesită apel de sistem pentru creare și comutare. Corutinele — primitive ale limbajului, nelegate de OS. Ele folosesc Continuation pentru salvarea stării și comută la nivel de runtime fără apeluri de sistem.

  • Memorie — fir ~1 MB, corutină ~100 de octeți. Diferență de 10 000 de ori
  • Creare — fir ~1 µs syscall, corutină ~0.01 µs la nivel JVM
  • Comutare — fir ~0.1 µs (syscall), corutină ~0.001 µs (continuation)
  • Maxim — mii de fire vs milioane de corutine pe dispozitiv
  • Anulare — firul nu poate fi anulat din exterior (deprecated Thread.stop), corutina poate prin Job.cancel()

Conform Google (2025), utilizarea corutinelor în locul firelor reduce consumul de memorie pentru sarcinile în fundal în aplicațiile Android cu 90–95%. Toate bibliotecile moderne Android (Retrofit, Room, WorkManager) au suport încorporat pentru corutine prin suspend functions. Ktor (framework-ul HTTP client al JetBrains) este, de asemenea, construit complet pe corutine, oferind suspend-funcții pentru fiecare cerere fără API callback. Room suportă corutinele prin suspend-funcții în DAO, permițând executarea interogărilor bazei de date fără blocarea firului principal.

Când să folosiți fire în loc de corutine

Firele rămân necesare pentru codul nativ prin JNI, apelurile blocante CPU-intensive fără limită de timp (redare video, simulări) și la integrarea cu bibliotecile C. Pentru orice altceva — corutinele.

Întrebări frecvente

Cu ce diferă corutina de un fir?

Corutina — este o unitate de lucru suspendabilă care se execută pe un fir existent. Firul este o resursă de sistem cu propriul stack. Corutinele sunt de mii de ori mai ușoare decât firele și nu blochează resursele la suspendare.

Ce este Dispatchers.IO și cu ce diferă de Default?

Dispatchers.IO este destinat operațiilor I/O blocante (rețea, fișiere) și poate crea fire noi când este necesar. Dispatchers.Default are un pool de dimensiune fixă (numărul de nuclee CPU) pentru calcule CPU-intensive.

Cum anulez o corutină lansată?

Job.cancel() anulează corutina și toți copiii săi. Pentru a verifica anularea în interiorul corutinei, folosiți ensureActive() — aruncă CancellationException dacă corutina este anulată.

Pot folosi corutine cu RxJava?

Da — prin biblioteca kotlinx-coroutines-rx3. Oferă funcțiile awaitSingle, awaitFirst și altele pentru conversia Observable/Single în suspend functions și invers prin flowable.

Ce este Flow în corutine?

Flow — un flux de date asincron rece, analogul RxJava Observable pentru corutine. Flow emite valori secvențial și se încheie cu o excepție sau succes. Suportă operatorii map, filter, catch și alții.

Concluzii

  • Coroutines — fire ușoare Kotlin cu suspendare fără blocare prin Continuation Passing Style
  • suspend — cuvânt cheie pentru marcarea funcțiilor suspendabile
  • Dispatchers gestionează pool-ul de fire: Main, IO, Default respectiv
  • CoroutineScope leagă ciclul de viață al corutinelor de componentă (Activity, ViewModel)
  • launch lansează corutina fără rezultat, async/await — cu rezultat
  • Structured Concurrency garantează anularea ierarhică a corutinelor copil
  • Corutine vs fire — corutinele sunt de 10 000 de ori mai ușoare și sunt standardul pentru Android

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și