ПрограммированиеKotlin Software Engineer

Как работают корутины в Kotlin? Опишите механизм запуска, диспетчеризацию, отмену и отработку ошибок. Приведите пример использования и разъясните нюансы, связанные с передачей контекста и исключениями.

Проходите собеседования с ИИ помощником Hintsage

Ответ

Корутины в Kotlin — это легковесные потоки с поддержкой приостановки и возобновления вычислений без блокирования потоков ОС. Запускаются через функции, такие как launch, async при наличии родительского CoroutineScope.

Диспетчеризация происходит через объекты CoroutineDispatcher, определяющие, в каком потоке/экзекьюторе исполняется корутина (Dispatchers.Main, Dispatchers.IO, Dispatchers.Default).

Пример:

fun main() = runBlocking { launch(Dispatchers.IO) { val data = getDataFromNetwork() withContext(Dispatchers.Main) { updateUI(data) } } }

Отмена: Корутины отменяются через отмену их job/Scope, что бросает внутреннее исключение CancellationException. Корутины должны периодически проверять флаг отмены либо вызывать приостанавливающие функции, чтобы корректно завершиться.

Обработка ошибок: Исключения в корутинах могут "тонуть" — например, если вы не обработаете ошибку в дочерней корутине, то она только завершит свою работу, а родитель не узнает о проблеме. Для этого есть механизмы SupervisorJob и CoroutineExceptionHandler.

Нюансы передачи контекста:

  • Контекст (например, Job, Dispatcher, пользовательские элементы) наследуется при создании новых корутин.
  • Нельзя просто передать параметры между scope'ами, если те закрыты или отменены.

Вопрос с подвохом

Что произойдет, если внутри родительской корутины одна из дочерних завершится с ошибкой, а другие продолжают работать? Будут ли отменены все дочерние?

Многие ошибочно полагают, что ошибка "остается в дочерней".

Правильный ответ: Если используется обычный Job (или launch), то все дочерние корутины автоматически отменяются при ошибке любой дочерней. Для того чтобы дочерние корутины не отменялись, используется SupervisorJob или supervisorScope:

supervisorScope { launch { error("fail") } launch { println("Этот код будет работать") } }

Примеры реальных ошибок из-за незнания тонкостей темы


История

Неправильная обработка ошибок — падение флоу: В проекте использовали корутины для загрузки данных. Исключения "тонули" внутри дочерних корутин, из-за чего уровень retry/recovery не работал, и главный экран оставался пустым без каких-либо сообщений об ошибке для пользователя.


История

Зависание UI из-за работы с Dispatchers.Main: Молодой Android-разработчик запустил тяжёлую вычислительную задачу на Dispatchers.Main — UI начал "замерзать". Не было понимания, что любые тяжёлые вычисления должны исполняться на Dispatchers.Default или Dispatchers.IO.


История

AbortError при отмене родительского scope: Один из разработчиков не учёл, что при отмене родительского скоупа все дочерние корутины завершаются отменой, и критически важная операция (сохранение данных) не была завершена.