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 — 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.
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.
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.
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.
Razumevanje unutrašnje strukture CoroutineScope zahteva poznavanje koncepta Job i principa strukturne konkurencije.
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:
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:
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.
CoroutineScope se može kreirati putem fabričke funkcije ili implementacijom interfejsa u svojoj klasi. Razmotrićemo oba pristupa.
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.
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.
Kotlin omogućava delegiranje implementacije CoroutineScope kroz ključnu reč by:
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 — je singleton CoroutineScope za celu aplikaciju. Njegova upotreba u produkcijskom kodu zvanično se ne preporučuje.
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()).
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.
Obe funkcije su suspend funkcije koje kreiraju privremeni scope za paralelne zadatke, ali njihovo ponašanje pri izuzecima se suštinski razlikuje.
| Karakteristika | coroutineScope | supervisorScope |
|---|---|---|
| Ponašanje pri grešci | Izuzetak u korutini deteta otkazuje sve ostale | Izuzetak u korutini deteta NE otkazuje ostale |
| Propagacija greške | Da, prvi izuzetak se propagira napolje | Da, prvi izuzetak se propagira napolje |
| Podrazumevani Job | Job() — deca su vezana za roditelja | SupervisorJob() — deca ne zavise jedno od drugog |
| Tipičan use-case | Atomska operacija od više koraka | Nezavisni paralelni zadaci (UI učitavanja) |
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.
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.
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.
Razmotrićemo najčešće greške programera pri korišćenju CoroutineScope u Kotlinu.
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.
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.
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.
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
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.
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.
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.
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.
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
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође