Собеседование. Часть 4: Корутины — от иллюзии потоков до Structured Concurrency и обработки ошибок
Многопоточность вызывает мандраж. В Android на смену RxJava пришли Kotlin Coroutines. За их лаконичным синтаксисом скрывается жесткая архитектура. Мой следующий блок вопросов проверяет, понимает ли кандидат, что именно происходит под капотом привычного launch.
Уровень 1: Иллюзия потоков (База)
Начинаю с провокации: «Корутина — это просто легковесный поток?»
Многие кивают, и это ловушка. Правильный ответ: Нет, это блок кода, умеющий приостанавливать работу (suspend) и возобновляться позже. На одном Main Thread могут поочередно работать тысячи корутин, не блокируя его. Это кооперативная многозадачность. Если кандидат отличает блокировку Thread.sleep от delay, базовый тест пройден.
Уровень 2: Кто за рулем? (Scope и Job)
Усложняем: «Мы запустили сетевой запрос. Юзер закрыл экран. Как избежать утечки памяти?»
Продвинутый кандидат назовет CoroutineScope и Job. Job управляет жизненным циклом задачи, а Scope — ее область видимости. Обязательный вопрос: «В чем фишка viewModelScope?» Ожидаемый ответ: он жестко привязан к ViewModel и автоматически вызывает cancel() дочерним корутинам в onCleared().
class MyViewModel : ViewModel() { fun loadData() { viewModelScope.launch { val data = api.fetchData() // Отменится сам при смерти ViewModel } } }
Уровень 3: Эффект домино (Structured Concurrency)
Меняем условия: «Запущено несколько задач в viewModelScope. Одна упала с Exception. Что будет?»
Джуны часто пишут try-catch внутри launch. Но истинный эксперт знает про Structured Concurrency. Корутины связаны иерархией. Если "ребенок" падает, он отменяет родителя (наш launch). Родитель умирает и отменяет остальных детей. В итоге молча упадет весь viewModelScope. Блок try-catch внутри вложенного launch здесь абсолютно бесполезен — исключение просто пробивает иерархию Job.
Уровень 4: Изоляция катастрофы (SupervisorJob)
Финальный босс: «Как защитить родительский scope от падения одной дочерней корутины?»
Здесь эксперт должен использовать supervisorScope. Его суперспособность — изоляция ошибок: смерть одной корутины не убивает соседей по scope.
class MyViewModel : ViewModel() { fun loadData() { viewModelScope.launch { val data = api.fetchData() // Отменится сам при смерти ViewModel } } }
А для глобальной обработки непойманных исключений (чтобы не крашнулось всё приложение) кандидат должен упомянуть CoroutineExceptionHandler, который передается в контекст корневой корутины.
Резюме: Суровая реальность против тепличного кода
Корутины расслабляют своим красивым синтаксисом. Для меня на интервью важно увидеть, что человек понимает последствия своих архитектурных решений.
В мобильной (и особенно Automotive) разработке сеть нестабильна. Машина заезжает в альпийский тоннель, связь обрывается, и один упавший сетевой запрос погоды не должен утянуть за собой весь модуль навигации из-за неправильно настроенной иерархии Job. Именно умение строить отказоустойчивые системы отличает кодера от инженера, которому можно смело доверить продакшен.




