CoroutineScope — шта је, област живота и рад у корутинама

Аутор: IT Sectr Објављено: 2026-06-22 Време читања: 10 мин

CoroutineScope — je Kotlin interfejs koji definiše oblast života korutine i pruža kontekst za pokretanje novih korutina. Prema dokumentaciji Kotlina, 2025, svaka instanca CoroutineScope sadrži CoroutineContext i upravlja svim korutinama pokrenutim u njemu. Kada se scope završi (cancel), sve korutine deteta se automatski otkazuju, što sprečava curenje memorije.

Glavno

  • CoroutineScope — interfejs sa jednim poljem CoroutineContext, koji definiše životni ciklus korutina
  • Job — element konteksta odgovoran za otkazivanje: otkazivanje scope-a otkazuje sve korutine deteta
  • Strukturna konkurencija — princip pri kome su korutine deteta vezane za roditeljski scope
  • GlobalScope — scope za celu aplikaciju, koji se ne preporučuje zbog rizika od curenja
  • supervisorScope — specijalni scope u kome otkazivanje jedne korutine deteta ne otkazuje ostale

Šta je CoroutineScope u Kotlinu?

CoroutineScope — je fundamentalni interfejs iz biblioteke kotlinx.coroutines koji služi kao kontejner za korutine. On definiše granice života korutina: kada se scope završi, sve korutine unutar njega se automatski otkazuju.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

Interfejs sadrži samo jedno polje — coroutineContext. Preko njega scope pruža dispečera (Dispatcher), zadatak (Job), obradu izuzetaka i druge elemente konteksta za sve korutine pokrenute u njemu.

Uloga u biblioteci kotlinx.coroutines

Sve funkcije za pokretanje korutina — launch, async, runBlocking — su ekstenzione funkcije na CoroutineScope. To znači da se mogu pozvati samo uz prisustvo objekta scope. Ovakav dizajn garantuje da svaka korutina ima jasno definisanog roditelja i životni ciklus.

Gde se primenjuje CoroutineScope

U Androidu svaka arhitektonska komponenta ima svoj scope: viewModelScope za ViewModel, lifecycleScope za Activity/Fragment. U serverskim aplikacijama scope može biti vezan za HTTP zahtev ili za pool konekcija sa bazom podataka.

Kako radi CoroutineScope: Job i strukturna konkurencija

Razumevanje unutrašnje strukture CoroutineScope zahteva poznavanje koncepta Job i principa strukturne konkurencije.

Job — zadatak korutine

Svaka korutina pri pokretanju vraća objekat Job (ili Deferred za async). Job predstavlja zadatak sa definisanim životnim ciklusom: New, Active, Completing, Completed, Cancelling, Cancelled. Job objekti formiraju strukturu stabla:

  • Roditeljski Job — scope u kome je pokrenuta korutina
  • Detetov Job — svaka korutina pokrenuta kroz launch/async
  • Otkazivanje roditelja → otkazivanje sve dece
  • Izuzetak u detetu → otkazivanje roditelja (osim supervisorScope)

Princip strukturne konkurencije

Strukturna konkurencija — ključni arhitektonski princip Kotlin Coroutines, pri kome je vek trajanja korutine vezan za vek trajanja njenog scope-a. Ovo je u suprotnosti sa modelom „fire-and-forget”, gde korutina nastavlja da živi nakon završetka scope-a. Prednosti strukturne konkurencije:

  • Predvidiv životni ciklus — kada se scope završi, sve korutine su garantovano zaustavljene
  • Automatska obrada grešaka — izuzetak u bilo kojoj korutini deteta se širi na scope
  • Bez curenja — nijedna korutina ne ostaje aktivna nakon završetka scope-a
  • Jasna hijerarhija — kod odražava logičku strukturu paralelnih operacija

Životni ciklus CoroutineScope

Kada se pozove scope.cancel(), Job scope-a prelazi u stanje Cancelled, što rekurzivno otkazuje sve detetove Job-ove. Nakon otkazivanja, scope se može ponovo koristiti samo kreiranjem nove instance CoroutineScope.

Kreiranje i podešavanje CoroutineScope

CoroutineScope se može kreirati putem fabričke funkcije ili implementacijom interfejsa u svojoj klasi. Razmotrićemo oba pristupa.

Fabrička funkcija CoroutineScope()

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

scope.launch {
    println("Покренуто на ${Thread.currentThread().name}")
}

Fabrička funkcija prihvata CoroutineContext i kreira scope sa navedenim kontekstom. U primeru se koristi Dispatchers.Default za CPU-intenzivne zadatke i SupervisorJob koji izoluje izuzetke između korutina deteta.

Implementacija interfejsa kroz kompoziciju

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

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

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

Čuvamo scope kao polje klase i ručno pozivamo cleanup za njegovo otkazivanje. Ovo je pogodno za komponente sa upravljanim životnim ciklusom — na primer, za repozitorijume ili menadžere.

Implementacija kroz delegaciju

Kotlin omogućava delegiranje implementacije CoroutineScope kroz ključnu reč by:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // корутина ради у DataLoader scope-у
        }
    }
}

Ovakav pristup je zgodan kada je klasa sama scope i želi da pruži metode za pokretanje korutina. Međutim, budite oprezni: klasa nasleđuje sve metode CoroutineScope, uključujući cancel, što može narušiti enkapsulaciju.

GlobalScope protiv prilagođenog CoroutineScope

GlobalScope — je singleton CoroutineScope za celu aplikaciju. Njegova upotreba u produkcijskom kodu zvanično se ne preporučuje.

Problemi GlobalScope

  • Nedostatak strukturne konkurencije — korutine u GlobalScope nisu vezane za životni ciklus komponente
  • Curenje memorije — korutina može nastaviti da se izvršava nakon zatvaranja Activity/Fragment
  • Otežano testiranje — GlobalScope se ne može zameniti u testovima
  • Nekontrolisana potrošnja resursa — mnoge korutine mogu raditi duže nego što se očekuje

Kada je GlobalScope opravdan

JetBrains dozvoljava GlobalScope samo u retkim scenarijima: pozadinski procesi na nivou aplikacije koji treba da žive i nakon zatvaranja svih Activity (na primer, sinhronizacija podataka, analitika). Ali čak i u ovim slučajevima poželjno je kreirati sopstveni scope sa CoroutineScope(SupervisorJob()).

Preporuka

Uvek koristite prilagođeni CoroutineScope sa eksplicitnim upravljanjem životnim ciklusom. U Androidu su to viewModelScope i lifecycleScope. U serverskim aplikacijama kreirajte scope za svaki zahtev ili pool konekcija.

coroutineScope vs supervisorScope: u čemo je razlika

Obe funkcije su suspend funkcije koje kreiraju privremeni scope za paralelne zadatke, ali njihovo ponašanje pri izuzecima se suštinski razlikuje.

KarakteristikacoroutineScopesupervisorScope
Ponašanje pri grešciIzuzetak u korutini deteta otkazuje sve ostaleIzuzetak u korutini deteta NE otkazuje ostale
Propagacija greškeDa, prvi izuzetak se propagira napoljeDa, prvi izuzetak se propagira napolje
Podrazumevani JobJob() — deca su vezana za roditeljaSupervisorJob() — deca ne zavise jedno od drugog
Tipičan use-caseAtomska operacija od više korakaNezavisni paralelni zadaci (UI učitavanja)

Kada izabrati coroutineScope

Koristite coroutineScope kada više paralelnih operacija čini jedinstvenu atomsku operaciju. Na primer, učitavanje podataka sa tri servera: ako jedan zahtev padne, ostali nemaju smisla.

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

Ako getProduct ili getReviews bace izuzetak — obe korutine su otkazane, a izuzetak se propagira pozivajućem kodu.

Kada izabrati supervisorScope

Koristite supervisorScope kada paralelne operacije ne zavise jedna od druge. Na primer, učitavanje podataka profila u više nezavisnih sekcija: ako sekcija preporuka padne, zaglavlje profila i lista prijatelja treba da se prikažu.

Tipične greške pri radu sa CoroutineScope

Razmotrićemo najčešće greške programera pri korišćenju CoroutineScope u Kotlinu.

Greška 1: Zaboravili ste da otkažete scope

Najčešći scenario curenja korutine — kreiranje scope bez poziva cancel pri završetku komponente. Ako scope nije otkazan, korutine nastavljaju da rade, držeći reference na objekte. U Androidu koristite viewModelScope ili lifecycleScope, koji se otkazuju automatski.

Greška 2: Korišćenje GlobalScope u Activity ili Fragment

GlobalScope ignoriše životni ciklus Android komponenti. Korutina pokrenuta u GlobalScope nakon zatvaranja Activity će nastaviti izvršavanje i pokušati da ažurira UI — što će dovesti do crash-a. Uvek koristite lifecycleScope za UI komponente.

Greška 3: Ponovno korišćenje otkazanog scope-a

Nakon poziva cancel() scope se ne može ponovo koristiti — sve korutine u njemu su već završene. Kreirajte novu instancu CoroutineScope putem fabričke funkcije. Job() ne podržava ponovnu aktivaciju.

Greška 4: Neispravno delegiranje interfejsa CoroutineScope

Pri delegiranju putem by, klasa dobija javnu metodu cancel() koja može biti pozvana sa bilo kog mesta, narušavajući enkapsulaciju. Čuvajte scope kao privatno polje, a ne delegirajte interfejs.

Često postavljana pitanja

Po čemu se CoroutineScope razlikuje od CoroutineContext?

CoroutineScope — je interfejs koji poseduje CoroutineContext i odgovoran je za životni ciklus korutina. CoroutineContext — je skup elemenata (dispečer, job, obrada grešaka) koji definiše „kako” se korutina izvršava. Jedna od razlika: scope kreira korutine, context upravlja njihovim ponašanjem.

Može li se kreirati CoroutineScope sa SupervisorJob?

Da, ovo je standardni obrazac: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob sprečava kaskadno otkazivanje korutina deteta pri izuzetku u jednoj od njih. Ovo je korisno za nezavisne paralelne zadatke gde greška u jednom ne bi trebalo da zaustavi druge.

Koliko korutina može da sadrži CoroutineScope?

Ne postoje ograničenja za broj korutina u scope-u — one su ograničene samo dostupnom memorijom i podešavanjima dispečera. Praktična granica obično iznosi hiljade aktivnih korutina u jednom scope-u. Međutim, veliki broj korutina može ukazivati na arhitektonske probleme.

Kako testirati kod sa CoroutineScope?

Ispravan način je da prenesete scope u klasu kroz konstruktor ili koristite runBlockingTest / runTest iz kotlinx-coroutines-test. U testovima možete zameniti scope sa TestCoroutineDispatcher i ručno kontrolisati izvršavanje korutina.

Može li jedna korutina imati svoj sopstveni scope?

Ne, scope je spoljašnji kontejner za korutinu. Sama korutina nije scope. Međutim, unutar korutine se može kreirati novi scope putem coroutineScope ili supervisorScope za paralelno pokretanje korutina deteta.

Sumiranje

  • CoroutineScope — interfejs sa poljem coroutineContext, koji definiše životni ciklus korutina pokrenutih u njemu
  • Strukturna konkurencija — otkazivanje scope automatski otkazuje sve korutine deteta, sprečavajući curenje memorije
  • Job i SupervisorJob — dva načina obrade grešaka: kaskadno otkazivanje (Job) i izolovane greške (SupervisorJob)
  • GlobalScope — ne preporučuje se za produkciju zbog nedostatka povezanosti sa životnim ciklusom
  • coroutineScope vs supervisorScope — atomske paralelne operacije protiv nezavisnih paralelnih zadataka
  • viewModelScope i lifecycleScope — gotovi scope-ovi za Android, automatski otkazani pri završetku komponente
  • Fabrička funkcija — preferirani način kreiranja scope-a kroz CoroutineContext + eksplicitni poziv cancel

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође