Как работает состояние машины в корутинах при приостановке и возобновлении
Что проверяют
Понимаете ли вы, что корутина на уровне байткода это не поток и не магия рантайма, а функция, которую компилятор разрезал на куски. Следом обычно спрашивают, где хранятся локальные переменные во время приостановки и на каком потоке корутина продолжится.
Как отвечать
Ключевое слово здесь одно: suspend. Никакого resume в языке нет, это метод Continuation. Компилятор делает с suspend-функцией следующее:
- CPS-преобразование: к сигнатуре добавляется скрытый параметр
Continuation<T>, а тип результата становитсяAny?. Функция возвращает либо значение, либо маркерCOROUTINE_SUSPENDED; - машина состояний: тело режется по точкам приостановки, то есть по вызовам других suspend-функций. Генерируется наследник
ContinuationImplс полемlabelи полями под локальные переменные, нужные после точки; - приостановка: перед вызовом функция сохраняет живые переменные в поля, выставляет
labelна следующий шаг и, получивCOROUTINE_SUSPENDED, возвращает его вверх по цепочке. Поток освобождается; - возобновление: кто-то вызывает
resumeWith(result), это приводит кinvokeSuspend(), где ветвление поlabelпрыгает к нужному куску, достаёт переменные из полей и продолжает.
Покажите на примере: три точки приостановки дают четыре состояния. На каком потоке продолжить, решает диспетчер из CoroutineContext: на Android viewModelScope работает на Dispatchers.Main.immediate, а после withContext(Dispatchers.IO) корутина возвращается на главный поток. Колбэчный API в эту модель заворачивают через suspendCancellableCoroutine.
Где спотыкаются
Называют корутины «лёгкими потоками» и на этом останавливаются. Метафора годится для вступления, но интервьюер ждёт Continuation, label и маркер приостановки.
Думают, что любой вызов suspend-функции обязательно приостанавливает корутину. Если результат готов сразу, функция просто вернёт значение без COROUTINE_SUSPENDED и без смены потока.
Считают, что код после точки приостановки выполнится на том же потоке. С Dispatchers.Default или Dispatchers.IO поток может смениться, поэтому ThreadLocal без asContextElement() теряет значение.