Как работает приостановка корутины
Что проверяют
Понимаете ли вы, что за словом suspend стоит преобразование кода компилятором, а не отдельный поток. Следом обычно спрашивают, на каком потоке корутина продолжит работу после приостановки и что будет, если внутри вызвать блокирующую функцию.
Как отвечать
Корутину запускает билдер (launch, async), а suspend лишь помечает функцию, которая может приостановиться. Механика:
- CPS-преобразование: компилятор добавляет в каждую
suspend-функцию скрытый параметрContinuation, то есть «что делать после возврата»; - машина состояний: тело режется по точкам приостановки на участки, номер текущего хранится в поле
label, а нужные дальше локальные переменные переезжают в поля объекта-продолжения; - приостановка: вызванная функция возвращает маркер
COROUTINE_SUSPENDED, и цепочка вызовов разматывается. Поток освобождается и берёт другую работу; - возобновление: когда результат готов, вызывается
continuation.resumeWith(result), диспетчер ставит продолжение в очередь своего потока, и машина прыгает на следующийlabel.
Реально приостанавливают только примитивы вроде suspendCancellableCoroutine, на них построены delay, withContext и suspend-вызовы Retrofit. На Android это значит: после withContext(Dispatchers.IO) код вернётся на Dispatchers.Main, а в Dispatchers.IO и Default продолжение может выполниться уже на другом потоке пула.
Где спотыкаются
Считают, что модификатор suspend сам делает функцию асинхронной. Если внутри тяжёлый цикл или Thread.sleep, поток заблокируется, и на Main это ANR.
Путают приостановку с блокировкой. delay(1000) не держит поток секунду, а Thread.sleep(1000) держит.
Забывают, что отмена кооперативна и срабатывает в точках приостановки. Долгий цикл без ensureActive() или yield() продолжит работать после cancel(), например после очистки ViewModel и отмены viewModelScope.