За счет чего возможно неблокирующее поведение coroutines
Что проверяют
Понимаете ли вы механику корутин, а не только то, что они «лёгкие потоки». Главный следующий вопрос: делает ли слово suspend функцию неблокирующей само по себе.
Как отвечать
Неблокирующее поведение складывается из трёх вещей:
- преобразование компилятором: каждая
suspend-функция компилируется в стиле передачи продолжения. Компилятор добавляет скрытый параметрContinuationи превращает тело функции в машину состояний, где каждая точка приостановки это отдельное состояние. Локальные переменные переезжают в объект продолжения в куче, поэтому корутины Kotlin называют бесстековыми; - приостановка без блокировки: в точке приостановки функция не ждёт, а возвращает маркер
COROUTINE_SUSPENDED, и поток свободен. Когда результат готов, кто-то вызываетcontinuation.resumeWith(result); - диспетчер: решает, на каком потоке продолжить.
Dispatchers.Mainотправляет продолжение в очередь главного потока черезHandler,Dispatchers.Defaultдержит пул по числу ядер,Dispatchers.IOпо умолчанию допускает до 64 потоков (или больше, если ядер больше) для блокирующих вызовов.
Отсюда ключевая мысль: неблокирующей операцию делает не suspend, а то, что внутри. delay не спит, а ставит возобновление в планировщик. Retrofit и Room поддерживают suspend нативно. Колбэчный API оборачивают в suspendCancellableCoroutine. А обычный блокирующий вызов переносят через withContext(Dispatchers.IO): он по-прежнему блокирует поток, но уже поток пула, а не главный.
Добавьте, что планирование кооперативное: отмена и переключение возможны только в точках приостановки.
Где спотыкаются
Считают, что suspend делает любой код асинхронным. Thread.sleep или синхронный сетевой вызов внутри suspend-функции на Dispatchers.Main подвесят интерфейс и приведут к ANR так же, как без корутин.
Используют runBlocking в Android-коде. Он блокирует текущий поток до завершения всех дочерних корутин, на главном потоке это прямая дорога к ANR.
Пишут длинный вычислительный цикл без точек приостановки и удивляются, что cancel() не срабатывает. В такой цикл нужно вставлять ensureActive() или yield().