CoroutineScope — е Kotlin интерфейс, който определя обхвата на живот на корутината и предоставя контекст за стартиране на нови корутини. Според документацията на Kotlin, 2025, всяка инстанция на CoroutineScope съдържа CoroutineContext и управлява всички корутини, стартирани в него. Когато scope приключи (cancel), всички дочерни корутини се автоматично анулират, което предотвратява изтичането на памет.
Основни моменти
CoroutineScope — е фундаментален интерфейс от библиотеката kotlinx.coroutines, който служи като контейнер за корутини. Той определя границите на живот на корутините: когато scope приключи, всички корутини в него се анулират автоматично.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Интерфейсът съдържа само едно поле — coroutineContext. Чрез него scope предоставя диспетчър (Dispatcher), задача (Job), обработка на изключения и други елементи от контекста за всички корутини, стартирани в него.
Всички функции за стартиране на корутини — launch, async, runBlocking — са разширяващи функции на CoroutineScope. Това означава, че те могат да бъдат извикани само при наличието на обект scope. Такъв дизайн гарантира, че всяка корутина има ясно определен родител и жизнен цикъл.
В Android всяка архитектурна компонента има свой scope: viewModelScope за ViewModel, lifecycleScope за Activity/Fragment. В сървърни приложения scope може да бъде свързан с HTTP заявка или с пул от връзки към база данни.
Разбирането на вътрешната структура на CoroutineScope изисква познаване с концепцията за Job и принципа на структурната конкуренция.
Всяка корутина при стартиране върща обект Job (или Deferred за async). Job представлява задача с определен жизнен цикъл: New, Active, Completing, Completed, Cancelling, Cancelled. Обектите Job образуват дървовидна структура:
Структурна конкуренция — ключов архитектурен принцип на Kotlin Coroutines, при който животът на корутината е свързан с живота на нейния scope. Това е в противопоставка на модела „fire-and-forget”, където корутината продължава да живее след края на scope. Предимства на структурната конкуренция:
Когато се извика scope.cancel(), Job на scope преминава в състояние Cancelled, което рекурсивно анулира всички дочерни Job. След анулиране, scope може да бъде повторно използван само чрез създаване на нова инстанция на CoroutineScope.
CoroutineScope може да бъде създаден чрез фабрична функция или чрез имплементиране на интерфейса в вашия клас. Нека разгледаме и двата подхода.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Работи на ${Thread.currentThread().name}")
}
Фабричната функция приема CoroutineContext и създава scope с посочения контекст. В примера се използва Dispatchers.Default за CPU-интензивни задачи и SupervisorJob, който изолира изключения между дочерните корутини.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
Съхраняваме scope като поле на класа и ръчно извикваме cleanup за неговото анулиране. Това е подходящо за компоненти с управляван жизнен цикъл — например, за репозитория или менийджъри.
Kotlin позволява делегиране на имплементацията на CoroutineScope чрез клюовата дума by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// корутината работи в scope DataLoader
}
}
}
Тази процедура е удобна, когато класът сам е scope и иска да предоставя методи за стартиране на корутини. Въпреки това, бъдете внимателни: класът наследява всички методи на CoroutineScope, включително cancel, което може да наруши капсулацията.
GlobalScope — е синглтон CoroutineScope за цялата приложения. Неговото използване в производствен код официално не се препоръчва.
JetBrains позволява GlobalScope само в редки сценарии: фонови процеси на ниво приложение, които трябва да живеят дори след затварянето на всички Activity (например, синхронизация на данни, аналитика). Но дори и в тези случаи е по-добре да създадете собствен scope с CoroutineScope(SupervisorJob()).
Винаги използвайте персонализиран CoroutineScope с изрично управление на жизнения цикъл. В Android това са viewModelScope и lifecycleScope. В сървърни приложения създавайте scope за всяка заявка или пул от връзки.
И двете функции са suspend функции, които създават временен scope за паралелни задачи, но поведението им при изключения се различава съществено.
| Характеристика | coroutineScope | supervisorScope |
|---|---|---|
| Поведение при грешка | Изключение в дочерна корутина анулира всички останали | Изключение в дочерна корутина НЕ анулира останалите |
| Разпространение на грешка | Да, първото изключение се разпространява навън | Да, първото изключение се разпространява навън |
| Подразбираем Job | Job() — децата са свързани с родителя | SupervisorJob() — децата не зависят едно от друго |
| Типичен случай на употреба | Атомна операция от няколко стъпки | Независими паралелни задачи (UI зареждане) |
Използвайте coroutineScope, когато няколко паралелни операции образуват единна атомна операция. Например, зареждане на данни от три сървъра: ако една заявка се провали, останалите нямат смисъл.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Ако getProduct или getReviews хвърлят изключение — и двете корутини се анулират и изключението се разпространява към викащия код.
Използвайте supervisorScope, когато паралелните операции не зависят една от друга. Например, зареждане на данни на профил в няколко независими секции: ако секцията за препоръки се провали, заглавието на профила и списъкът на приятелите трябва да се покажат.
Нека разгледаме най-честите грешки на разработчиците при използването на CoroutineScope в Kotlin.
Най-честият сценарий за изтичане на корутина — създаване на scope без извикване на cancel при приключването на компонентата. Ако scope не е анулиран, корутините продължават да работят, задържайки референции към обекти. В Android използвайте viewModelScope или lifecycleScope, които се анулират автоматично.
GlobalScope игнорира жизнения цикъл на Android компонентите. Корутина, стартирана в GlobalScope след затварянето на Activity, ще продължи да работи и ще се опита да актуализира UI — което ще доведе до краш. Винаги използвайте lifecycleScope за UI компоненти.
След извикване на cancel() scope не може да бъде използван повторно — всички корутини в него вече са завършени. Създайте нова инстанция на CoroutineScope чрез фабричната функция. Job() не поддържа повторно активиране.
При делегиране чрез by, класът получава публичен метод cancel(), който може да бъде извикан отвсякъде, нарушавайки капсулацията. Съхранявайте scope като частно поле, не делегирайте интерфейса.
Често задавани въпроси
CoroutineScope — е интерфейс, който притежава CoroutineContext и е отговорен за жизнения цикъл на корутините. CoroutineContext — е набор от елементи (dispatcher, job, обработка на грешки), който определя „как” се изпълнява корутината. Една от разликите: scope създава корутини, context управлява поведението им.
Да, това е стандартен модел: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob предотвратява каскадно анулиране на дочерните корутини при изключение в една от тях. Това е полезно за независими паралелни задачи, където грешка в здача не трябва да спира другите.
Няма ограничения за броя корутини в scope — те са ограничени само от наличната памет и настройките на dispatcher-а. Практическият лимит обикновено е хиляди активни корутини в един scope. Въпреки това, голям брой корутини може да показва архитектурни проблеми.
Правилният начин е да предадете scope на класа чрез конструктора или да използвате runBlockingTest / runTest от kotlinx-coroutines-test. В тестовете можете да замените scope с TestCoroutineDispatcher и ръчно да контролирате изпълнението на корутините.
Не, scope е външен контейнер за корутината. Самата корутина не е scope. Въпреки това, внутре в корутина може да се създаде нов scope чрез coroutineScope или supervisorScope за паралелно стартиране на дочерни корутини.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също