Этот вопрос проверяет понимание структурированной конкурентности в Kotlin и типичных ошибок, которые её нарушают.
Structured Concurrency – это подход, при котором корутины запускаются в определённой области (CoroutineScope), и их жизненный цикл связан с этой областью. Если область отменяется, отменяются и все её корутины.
Нарушить Structured Concurrency можно:
Запуская корутины в GlobalScope (они живут независимо от контекста).
Создавая корутины без явного Job или SupervisorJob.
Используя launch или async вне CoroutineScope.
1. Как работает Structured Concurrency?
Каждая корутина запускается внутри CoroutineScope (например, viewModelScope, lifecycleScope).
Если область (scope) отменяется, автоматически отменяются все её корутины.
Это предотвращает утечки памяти и неконтролируемые фоновые операции.
Пример правильного использования:
viewModelScope.launch {
val data = fetchData() // Если ViewModel очищается, корутина отменяется
updateUi(data)
} 2. Как нарушить Structured Concurrency?
Запуск в GlobalScope
GlobalScope.launch { // ❌ Корутина живёт всё время работы приложения
doSomething() // Если экран закрыт, она продолжит работать
} Проблема: Корутина не привязана к жизненному циклу компонента (Activity, ViewModel).
Неявный Job без отмены
fun startBackgroundTask() {
val job = launch { // ❌ Где scope? Если он не передан, корутина может висеть в памяти
heavyWork()
}
} Проблема: Нет контроля над отменой, возможна утечка.
Потеря ссылки на Job
fun startTask() {
val job = viewModelScope.launch {
delay(1000)
updateUi() // Может сработать после очистки ViewModel
}
// job не отслеживается и не отменяется явно
} Проблема: Если экран закрыт до завершения delay, updateUi() вызовет краш.
3. Как исправить?
Всегда использовать viewModelScope, lifecycleScope или явный CoroutineScope с Job.
Отменять корутины при очистке ресурсов (onCleared, onDestroy).
Использовать coroutineScope или supervisorScope для вложенных корутин.
Пример безопасного кода:
class MyViewModel : ViewModel() {
private val customScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun fetchData() {
customScope.launch {
try {
val result = apiCall()
withContext(Dispatchers.Main) { updateUi(result) }
} catch (e: Exception) { handleError(e) }
}
}
override fun onCleared() {
customScope.cancel() // ✅ Все корутины отменяются при очистке ViewModel
}
} Вывод:
Structured Concurrency делает управление корутинами предсказуемым. Нарушая её, мы рискуем получить утечки памяти и неожиданное поведение. Всегда связывайте корутины с областью видимости!