CoroutineScope — je rozhraní Kotlin, které definuje oblast života korutiny a poskytuje kontext pro spouštění nových korutin. Podle dokumentace Kotlin, 2025, každá instance CoroutineScope obsahuje CoroutineContext a spravuje všechny korutiny v ní spuštěné. Když scope skončí (cancel), všechny podřízené korutiny jsou automaticky zrušeny, což zabraňuje únikům paměti.
Hlavní body
CoroutineScope — je základní rozhraní z knihovny kotlinx.coroutines, které slouží jako kontejner pro korutiny. Definuje hranice života korutin: když scope skončí, všechny korutiny uvnitř jsou automaticky zrušeny.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Rozhraní obsahuje pouze jedno pole — coroutineContext. Prostřednictvím něj scope poskytuje dispečera (Dispatcher), úkol (Job), zpracování výjimek a další prvky kontextu pro všechny korutiny v něm spuštěné.
Všechny funkce pro spouštění korutin — launch, async, runBlocking — jsou rozšiřující funkce na CoroutineScope. To znamená, že je lze volat pouze při existenci objektu scope. Takový návrh zaručuje, že každá korutina má jasně definovaného rodiče a životní cyklus.
V Androidu má každá architektonická komponenta svůj scope: viewModelScope pro ViewModel, lifecycleScope pro Activity/Fragment. V serverových aplikacích může být scope vázán na HTTP požadavek nebo na fond připojení k databázi.
Porozumění vnitřní struktuře CoroutineScope vyžaduje znalost konceptu Job a principu strukturální konkurence.
Každá korutina při spuštění vrátí objekt Job (nebo Deferred pro async). Job představuje úkol s definovaným životním cyklem: New, Active, Completing, Completed, Cancelling, Cancelled. Objekty Job tvoří stromovou strukturu:
Strukturální konkurence — klíčový architektonický princip Kotlin Coroutines, při kterém je životnost korutiny vázána na životnost jejího scope. To je v kontrastu s modelem „fire-and-forget”, kde korutina žije dále po skončení scope. Výhody strukturální konkurence:
Když je zavoláno scope.cancel(), Job scope přejde do stavu Cancelled, což rekurzivně zruší všechny podřízené Job. Po zrušení lze scope znovu použít pouze vytvořením nové instance CoroutineScope.
CoroutineScope lze vytvořit pomocí tovární funkce nebo implementací rozhraní ve vlastní třídě. Podíváme se na oba přístupy.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Běží na ${Thread.currentThread().name}")
}
Tovární funkce přijímá CoroutineContext a vytváří scope s uvedeným kontextem. V příkladu je použit Dispatchers.Default pro úkoly náročné na CPU a SupervisorJob, který izoluje výjimky mezi podřízenými korutinami.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
Ukládáme scope jako pole třídy a ručně voláme cleanup pro jeho zrušení. To je vhodné pro komponenty s řízeným životním cyklem — například pro repozitáře nebo správce.
Kotlin umožňuje delegovat implementaci CoroutineScope pomocí klíčového slova by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// korutina běží v scope DataLoader
}
}
}
Takový přístup je vhodný, když třída sama je scope a chce poskytovat metody spouštění korutin. Buďte však opatrní: třída dědí všechny metody CoroutineScope, včetně cancel, což může porušit zapouzdření.
GlobalScope — je singleton CoroutineScope pro celou aplikaci. Jeho použití v produkčním kódu se oficiálně nedoporučuje.
JetBrains povoluje GlobalScope pouze ve výjimečných scénářích: procesy na pozadí na úrovni aplikace, které musí být živé i po zavření všech Activity (například synchronizace dat, analytika). Ale i v těchto případech je lepší vytvořit vlastní scope s CoroutineScope(SupervisorJob()).
Vždy používejte vlastní CoroutineScope s explicitním řízením životního cyklu. V Androidu jsou to viewModelScope a lifecycleScope. V serverových aplikacích vytvářejte scope pro každý požadavek nebo fond připojení.
Obě funkce jsou suspend funkce, které vytvářejí dočasný scope pro paralelní úkoly, ale jejich chování při výjimkách se zásadně liší.
| Charakteristika | coroutineScope | supervisorScope |
|---|---|---|
| Chování při chybě | Výjimka v podřízené korutině zruší všechny ostatní | Výjimka v podřízené korutině NEzruší ostatní |
| Šíření chyby | Ano, první výjimka je šířena ven | Ano, první výjimka je šířena ven |
| Výchozí Job | Job() — děti jsou vázány na rodiče | SupervisorJob() — děti nezávisí na sobě |
| Typický případ použití | Atomická operace z více kroků | Nezávislé paralelní úkoly (UI načítání) |
Použijte coroutineScope, když několik paralelních operací tvoří jedinou atomickou operaci. Například načítání dat ze tří serverů: pokud jeden požadavek selže, ostatní nemají smysl.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Pokud getProduct nebo getReviews vyhodí výjimku — obě korutiny jsou zrušeny a výjimka je šířena do volajícího kódu.
Použijte supervisorScope, když paralelní operace nezávisí na sobě. Například načítání dat profilu ve více nezávislých sekcích: pokud sekce doporučení selže, záhlaví profilu a seznam přátel by se měly zobrazit.
Podíváme se na nejčastější chyby vývojářů při používání CoroutineScope v Kotlinu.
Nejčastější scénář úniku korutiny — vytvoření scope bez volání cancel při ukončení komponenty. Pokud scope není zrušen, korutiny pokračují v běhu a drží reference na objekty. V Androidu použijte viewModelScope nebo lifecycleScope, které se ruší automaticky.
GlobalScope ignoruje životní cyklus Android komponent. Korutina spuštěná v GlobalScope po zavření Activity bude pokračovat v běhu a pokusí se aktualizovat UI — což povede k pádu. Vždy používejte lifecycleScope pro UI komponenty.
Po volání cancel() nelze scope znovu použít — všechny korutiny v něm jsou již dokončeny. Vytvořte novou instanci CoroutineScope pomocí tovární funkce. Job() nepodporuje opětovnou aktivaci.
Při delegování pomocí by třída získá veřejnou metodu cancel(), kterou lze zavolat odkudkoli, což porušuje zapouzdření. Uchovávejte scope jako soukromé pole, nedelegujte rozhraní.
Často kladené otázky
CoroutineScope — je rozhraní, které vlastní CoroutineContext a je odpovědné za životní cyklus korutin. CoroutineContext — je sada prvků (dispečer, job, zpracování chyb), která určuje „jak“ se korutina provádí. Jedním z rozdílů je, že scope vytváří korutiny, context řídí jejich chování.
Ano, toto je standardní vzor: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob zabraňuje kaskádovému zrušení podřízených korutin při výjimce v jedné z nich. To je užitečné pro nezávislé paralelní úkoly, kde chyba v jednom by neměla zastavit ostatní.
Neexistují omezení počtu korutin v scope — jsou omezeny pouze dostupnou pamětí a nastavením dispečera. Praktický limit bývá obvykle tisíce aktivních korutin v jednom scope. Velký počet korutin však může naznačovat architektonické problémy.
Správným způsobem je předat scope třídě prostřednictvím konstruktoru nebo použít runBlockingTest / runTest z kotlinx-coroutines-test. V testech můžete scope nahradit TestCoroutineDispatcher a ručně řídit provádění korutin.
Ne, scope je vnější kontejner pro korutinu. Samotná korutina není scope. Uvnitř korutiny však lze vytvořit nový scope pomocí coroutineScope nebo supervisorScope pro paralelní spouštění podřízených korutin.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také