Что такое continuation в Kotlin корутинах
Что проверяют
Понимаете ли вы, что корутины это не магия и не потоки, а преобразование кода компилятором. Следом обычно спрашивают, как обернуть callback API в suspend функцию и что будет при отмене.
Как отвечать
Continuation<in T> это интерфейс из двух членов: context: CoroutineContext и resumeWith(result: Result<T>), плюс расширения resume и resumeWithException. По смыслу это «что делать дальше», когда результат будет готов.
Как это устроено:
- CPS-преобразование: компилятор превращает
suspend fun load(id: Int): Userв функцию с дополнительным параметромContinuation<User>и возвращаемым типомAny?. Она возвращает либо результат, либо маркерCOROUTINE_SUSPENDED; - машина состояний: тело функции разбивается на куски по точкам приостановки. Компилятор генерирует класс-наследник
ContinuationImplс полемlabelи сохранёнными локальными переменными. Объект создаётся один раз на вызов функции и переиспользуется между приостановками; - возобновление: кто-то вызывает
resumeWith, машина прыгает на нужныйlabelи продолжает. Исключение изresumeWithExceptionвыбрасывается в точке вызова, поэтомуtry/catchработает как обычно; - диспетчеризация:
ContinuationInterceptorиз контекста оборачивает continuation, иresumeWithуходит на поток диспетчера. ДляDispatchers.Mainэто пост в главныйLooper.
Прямо с continuation в прикладном коде работают в одном месте: мост к колбэкам через suspendCancellableCoroutine. Внутри вызываете API, в колбэке cont.resume(value), а в cont.invokeOnCancellation отменяете запрос. Так устроены suspend методы Retrofit.
Где спотыкаются
Используют suspendCoroutine вместо suspendCancellableCoroutine. При уходе с экрана viewModelScope отменяется, а сетевой вызов продолжает жить и держит ссылки.
Вызывают resume дважды, например из onSuccess и onComplete, и получают IllegalStateException с текстом Already resumed. Или не вызывают ни разу в ветке ошибки, и корутина висит вечно.
Считают, что слово suspend само уводит работу в фон. Приостановка лишь освобождает поток, а блокирующий вызов внутри неё на Dispatchers.Main всё так же вешает интерфейс.