Что такое GlobalScope и почему его следует избегать?
GlobalScope — синглтон-scope, живущий всё время работы приложения. Его следует избегать, поскольку он нарушает structured concurrency: корутины не отменяются при уничтожении компонента, провоцируют утечки и делают тесты недетерминированными. Используйте viewModelScope, lifecycleScope или собственный CoroutineScope.
GlobalScope и его проблемы
GlobalScope — это синглтон CoroutineScope, привязанный к жизни всего приложения. Корутины, запущенные в нём, живут, пока живёт JVM/приложение, и их нельзя отменить структурированно. Именно поэтому GlobalScope помечен аннотацией @DelicateCoroutinesApi — его использование требует явного осознанного решения.
Почему GlobalScope опасен
- Нарушение structured concurrency: корутина в GlobalScope не является дочерней ни для какого родительского scope. Если вы закрываете экран, ViewModel, сервис — корутина продолжает жить.
- Утечки ресурсов: сетевые соединения, держатели блокировок, ссылки на Activity/Fragment не освобождаются, пока корутина не завершится сама.
- Невозможность тестирования: тест не может получить Job корутины из GlobalScope, дождаться её завершения или отменить её детерминированно.
- Неявный диспетчер: по умолчанию —
Dispatchers.Default, что может быть неожиданным.
Пример проблемы
class MyViewModel : ViewModel() {
fun loadData() {
// ПЛОХО: корутина переживёт уничтожение ViewModel
GlobalScope.launch {
val data = repository.fetch()
_state.value = data // NPE или crash после onCleared()
}
}
}
Правильная альтернатива — viewModelScope
class MyViewModel : ViewModel() {
fun loadData() {
// ХОРОШО: корутина отменяется при onCleared()
viewModelScope.launch {
val data = repository.fetch()
_state.value = data
}
}
}
Когда GlobalScope всё же допустим
Есть редкие случаи, когда GlobalScope оправдан:
- Корутина действительно должна жить всё время работы приложения (фоновый daemon-процесс, top-level сервис мониторинга).
- Код в
main()серверного приложения без собственного lifecycle-scope. - Явно одноразовые fire-and-forget операции, потеря которых не критична.
Альтернативы для разных контекстов
// Android ViewModel
viewModelScope.launch { /* отменяется с ViewModel */ }
// Android Fragment/Activity
lifecycleScope.launch { /* отменяется с lifecycle */ }
lifecycleScope.launchWhenStarted { /* приостанавливается в STOPPED */ }
// Ktor / серверный код — собственный scope
val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
appScope.launch { /* управляемый scope */ }
// Тесты
TestScope().launch { /* детерминированный clock */ }
// Самостоятельный scope с явной отменой
class MyService {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun start() { scope.launch { doWork() } }
fun stop() { scope.cancel() }
}
Как компилятор предупреждает
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch { /* явно опт-ин */ }
Без @OptIn IDE покажет предупреждение. Наличие @OptIn в коде — сигнал для ревьюера: «автор знал, что делает».
Подводные камни
- Утечка Activity: захват
this(Activity/Fragment) в лямбде GlobalScope.launch → OutOfMemory после поворота экрана. - Тихий сбой после отмены контекста: обновление UI-элементов после их уничтожения приведёт к краш-репортам в продакшне.
- Скрытые зависимости: unit-тест завершается, но корутина GlobalScope ещё работает — тест проходит, а побочный эффект случается после assert.
- Диспетчер по умолчанию: GlobalScope использует
EmptyCoroutineContext, что означаетDispatchers.Default. IO-задачи там займут thread pool, предназначенный для вычислений. - Цепочка отмены рвётся: даже если родительский Job отменён, дочерняя GlobalScope-корутина этого не «почувствует» — она не является дочерней.
- Тестирование с runTest:
runTest(kotlinx-coroutines-test) контролирует только корутины внутри TestScope. GlobalScope-корутины выполняются вне его — тесты становятся недетерминированными.
Common mistakes
- Объяснять «GlobalScope» только как синтаксис и не описывать поведение runtime/compiler.
- Игнорировать важный риск: В обычном use case GlobalScope приводит к утечкам, потерянным исключениям и операциям после отмены запроса.
- Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.
What the interviewer is testing
- Формулирует суть темы «GlobalScope» своими словами и связывает ее с кодом.
- Называет механизм: Он допустим только для явно process-wide задач с собственным error handling и shutdown strategy.
- Видит production-последствие: В обычном use case GlobalScope приводит к утечкам, потерянным исключениям и операциям после отмены запроса.