CoroutineScope — ce este, domeniul de viață și funcționarea în corutine

Autor: IT Sectr Publicat: 2026-06-22 Timp de citire: 10 min

CoroutineScope — este o interfață Kotlin care definește domeniul de viață al corutinei și oferă context pentru lansarea de noi corutine. Potrivit documentației Kotlin, 2025, fiecare instanță CoroutineScope conține CoroutineContext și gestionează toate corutinele lansate în ea. Când scope se încheie (cancel), toate corutinele copil sunt anulate automat, prevenind scurgerile de memorie.

Puncte principale

  • CoroutineScope — interfață cu un singur câmp CoroutineContext, care definește ciclul de viață al corutinelor
  • Job — element de context responsabil de anulare: anularea scope anulează toate corutinele copil
  • Concurență structurală — principiul prin care corutinele copil sunt legate de scope-ul părinte
  • GlobalScope — scope pentru întreaga aplicație, nerecomandat din cauza riscului de scurgeri
  • supervisorScope — scope special în care anularea unei corutine copil nu le anulează pe celelalte

Ce este CoroutineScope în Kotlin?

CoroutineScope — este o interfață fundamentală din biblioteca kotlinx.coroutines care servește ca container pentru corutine. Aceasta definește limitele de viață ale corutinelor: când scope se încheie, toate corutinele din interior sunt anulate automat.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

Interfața conține un singur câmp — coroutineContext. Prin intermediul acestuia, scope furnizează dispatcher (Dispatcher), sarcină (Job), gestionarea excepțiilor și alte elemente de context pentru toate corutinele lansate în el.

Rolul în biblioteca kotlinx.coroutines

Toate funcțiile de lansare a corutinelor — launch, async, runBlocking — sunt funcții de extensie pe CoroutineScope. Aceasta înseamnă că pot fi apelate doar în prezența unui obiect scope. Un astfel de design garantează că fiecare corutină are un părinte și un ciclu de viață clar definite.

Unde se aplică CoroutineScope

În Android, fiecare componentă arhitecturală are propriul scope: viewModelScope pentru ViewModel, lifecycleScope pentru Activity/Fragment. În aplicațiile server, scope poate fi legat de o cerere HTTP sau de un pool de conexiuni cu baza de date.

Cum funcționează CoroutineScope: Job și concurență structurală

Înțelegerea structurii interne a CoroutineScope necesită familiarizarea cu conceptul de Job și principiul concurenței structurale.

Job — sarcina corutinei

Fiecare corutină la lansare returnează un obiect Job (sau Deferred pentru async). Job reprezintă o sarcină cu un ciclu de viață definit: New, Active, Completing, Completed, Cancelling, Cancelled. Obiectele Job formează o structură arborescentă:

  • Job părinte — scope-ul în care a fost lansată corutina
  • Job copil — fiecare corutină lansată prin launch/async
  • Anularea părintelui → anularea tuturor copiilor
  • Excepție în copil → anularea părintelui (cu excepția supervisorScope)

Principiul concurenței structurale

Concurența structurală — principiul arhitectural cheie al Kotlin Coroutines, în care durata de viață a corutinei este legată de durata de viață a scope-ului său. Acesta contrastează cu modelul „fire-and-forget”, unde corutina continuă să existe după încheierea scope-ului. Avantajele concurenței structurale:

  • Ciclu de viață previzibil — când scope se încheie, toate corutinele sunt garantat oprite
  • Gestionarea automată a erorilor — excepția în orice corutină copil se răspândește la scope
  • Fără scurgeri — nicio corutină nu rămâne activă după încheierea scope-ului
  • Ierarhie clară — codul reflectă structura logică a operațiunilor paralele

Ciclul de viață al CoroutineScope

Când este apelat scope.cancel(), Job-ul scope-ului trece în starea Cancelled, ceea ce anulează recursiv toate Job-urile copil. După anulare, scope poate fi reutilizat doar prin crearea unei noi instanțe CoroutineScope.

Crearea și configurarea CoroutineScope

CoroutineScope poate fi creat printr-o funcție fabrică sau prin implementarea interfeței în propria clasă. Să analizăm ambele abordări.

Funcția fabrică CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Rulează pe ${Thread.currentThread().name}")
}

Funcția fabrică primește CoroutineContext și creează scope cu contextul specificat. În exemplu se utilizează Dispatchers.Default pentru sarcini CPU-intensive și SupervisorJob care izolează excepțiile între corutinele copil.

Implementarea interfeței prin compoziție

kotlin
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    suspend fun fetchData(): Data = scope.async {
        api.getData()
    }.await()

    fun cleanup() {
        scope.cancel()
    }
}

Stocăm scope ca câmp al clasei și apelăm manual cleanup pentru anularea acestuia. Aceasta este potrivit pentru componente cu ciclu de viață gestionat — de exemplu, pentru repository-uri sau manageri.

Implementarea prin delegare

Kotlin permite delegarea implementării CoroutineScope prin cuvântul cheie by:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // corutina rulează în scope-ul DataLoader
        }
    }
}

Această abordare este convenabilă atunci când clasa înseși este scope și dorește să ofere metode de lansare a corutinelor. Cu toate acestea, fiți precauți: clasa moștenește toate metodele CoroutineScope, inclusiv cancel, ceea ce poate încălca encapsularea.

GlobalScope vs CoroutineScope personalizat

GlobalScope — este un singleton CoroutineScope pentru întreaga aplicație. Utilizarea sa în codul de producție nu este recomandată oficial.

Problemele GlobalScope

  • Lipsa concurenței structurale — corutinele în GlobalScope nu sunt legate de ciclul de viață al componentei
  • Scurgeri de memorie — corutina poate continua să ruleze după închiderea Activity/Fragment
  • Testare dificilă — GlobalScope nu poate fi înlocuit în teste
  • Consum necontrolat de resurse — multe corutine pot rula mai mult decât se așteaptă

Când GlobalScope este justificat

JetBrains permite GlobalScope doar în scenarii rare: procese de fundal la nivelul aplicației care trebuie să trăiască chiar și după închiderea tuturor Activity (de exemplu, sincronizarea datelor, analitica). Dar și în aceste cazuri este preferabil să creați propriul scope cu CoroutineScope(SupervisorJob()).

Recomandare

Utilizați întotdeauna CoroutineScope personalizat cu gestionare explicită a ciclului de viață. În Android, acestea sunt viewModelScope și lifecycleScope. În aplicațiile server, creați scope pentru fiecare cerere sau pool de conexiuni.

coroutineScope vs supervisorScope: care este diferența

Ambele funcții sunt funcții suspend care creează un scope temporar pentru sarcini paralele, dar comportamentul lor la excepții diferă fundamental.

CaracteristicăcoroutineScopesupervisorScope
Comportament la eroareExcepția în corutina copil anulează toate celelalteExcepția în corutina copil NU anulează celelalte
Propagarea eroriiDa, prima excepție este propagată în exteriorDa, prima excepție este propagată în exterior
Job implicitJob() — copiii sunt legați de părinteSupervisorJob() — copiii nu depind unii de alții
Caz tipic de utilizareOperație atomică din mai mulți pașiSarcini paralele independente (încărcări UI)

Când să alegeți coroutineScope

Utilizați coroutineScope atunci când mai multe operațiuni paralele formează o singură operație atomică. De exemplu, încărcarea datelor de pe trei servere: dacă o cerere eșuează, celelalte nu au sens.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

Dacă getProduct sau getReviews aruncă o excepție — ambele corutine sunt anulate, iar excepția este propagată codului apelant.

Când să alegeți supervisorScope

Utilizați supervisorScope atunci când operațiunile paralele nu depind unele de altele. De exemplu, încărcarea datelor de profil în mai multe secțiuni independente: dacă secțiunea de recomandări eșuează, antetul profilului și lista de prieteni ar trebui să se afișeze.

Erori tipice la lucrul cu CoroutineScope

Să analizăm cele mai frecvente erori ale dezvoltatorilor la utilizarea CoroutineScope în Kotlin.

Eroarea 1: Ați uitat să anulați scope

Cel mai frecvent scenariu de scurgere a corutinei — crearea scope fără a apela cancel la finalizarea componentei. Dacă scope nu este anulat, corutinele continuă să ruleze, reținând referințe la obiecte. În Android, utilizați viewModelScope sau lifecycleScope, care sunt anulate automat.

Eroarea 2: Utilizarea GlobalScope în Activity sau Fragment

GlobalScope ignoră ciclul de viață al componentelor Android. O corutină lansată în GlobalScope după închiderea Activity va continua să ruleze și să încearce să actualizeze UI — ceea ce va duce la un crash. Utilizați întotdeauna lifecycleScope pentru componentele UI.

Eroarea 3: Reutilizarea scope-ului anulat

După apelarea cancel(), scope nu poate fi reutilizat — toate corutinele din el sunt deja finalizate. Creați o nouă instanță CoroutineScope prin funcția fabrică. Job() nu suportă reactivarea.

Eroarea 4: Delegarea incorectă a interfeței CoroutineScope

La delegarea prin by, clasa primește metoda publică cancel() care poate fi apelată din orice loc, încălcând encapsularea. Păstrați scope ca un câmp privat, nu delegați interfața.

Întrebări frecvente

Cu ce diferă CoroutineScope de CoroutineContext?

CoroutineScope — este o interfață care deține CoroutineContext și este responsabilă pentru ciclul de viață al corutinelor. CoroutineContext — este un set de elemente (dispatcher, job, gestionarea erorilor) care definește „cum” se execută corutina. Una dintre diferențe: scope creează corutine, context gestionează comportamentul lor.

Se poate crea CoroutineScope cu SupervisorJob?

Da, acesta este un model standard: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob previne anularea în cascadă a corutinelor copil la o excepție într-una dintre ele. Acest lucru este util pentru sarcini paralele independente, unde o eroare într-una nu ar trebui să le oprească pe celelalte.

Câte corutine poate conține CoroutineScope?

Nu există limitări privind numărul de corutine într-un scope — ele sunt limitate doar de memoria disponibilă și setările dispatcher-ului. Limita practică este de obicei de mii de corutine active într-un singur scope. Cu toate acestea, un număr mare de corutine poate indica probleme arhitecturale.

Cum să testez codul cu CoroutineScope?

Modul corect este să transmiteți scope clasei prin constructor sau să utilizați runBlockingTest / runTest din kotlinx-coroutines-test. În teste, puteți înlocui scope cu TestCoroutineDispatcher și controla manual execuția corutinelor.

Poate o corutină să aibă propriul scope?

Nu, scope este un container extern pentru corutină. Corutina în sine nu este un scope. Cu toate acestea, în interiorul unei corutine se poate crea un nou scope prin coroutineScope sau supervisorScope pentru lansarea paralelă a corutinelor copil.

Rezumat

  • CoroutineScope — interfață cu câmpul coroutineContext, care definește ciclul de viață al corutinelor lansate în el
  • Concurența structurală — anularea scope anulează automat toate corutinele copil, prevenind scurgerile de memorie
  • Job și SupervisorJob — două moduri de gestionare a erorilor: anulare în cascadă (Job) și erori izolate (SupervisorJob)
  • GlobalScope — nerecomandat pentru producție din cauza lipsei de legare la ciclul de viață
  • coroutineScope vs supervisorScope — operații paralele atomice vs sarcini paralele independente
  • viewModelScope și lifecycleScope — scope-uri gata făcute pentru Android, anulate automat la finalizarea componentei
  • Funcția fabrică — modul preferat de a crea scope prin CoroutineContext + apel explicit cancel

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