В чем разница delay в корутине и sleep в потоке
Что проверяют
Понимаете ли вы разницу между приостановкой корутины и блокировкой потока и к чему она ведёт на главном потоке Android. Следом часто спрашивают, как delay ведёт себя при отмене и в тестах.
Как отвечать
Thread.sleep: блокирует текущий поток. Пока он спит, на нём ничего не выполняется: ни другие корутины того же диспетчера, ни отрисовка кадров, если это главный поток. Долгая блокировка main-потока даёт зависания и ANR;delay:suspend-функция. Корутина приостанавливается, её продолжение планируется на нужный момент, а поток свободен для другой работы. НаDispatchers.Mainэто отложенная задача черезHandler, и UI продолжает отрисовываться.
Отсюда следствия:
- отмена:
delayотменяется кооперативно. ЕслиviewModelScopeотменён, ожидание прерываетсяCancellationException, и код после него не выполнится.Thread.sleepпро отмену корутин не знает и реагирует только наThread.interrupt(); - масштаб: тысяча корутин в
delayпочти ничего не стоит, тысяча потоков вThread.sleepэто тысяча стеков в памяти; - тесты: в
runTestзадержки пропускаются за счёт виртуального времени, и тест сdelay(5_000)проходит мгновенно.Thread.sleepчестно ждёт и замедляет прогон; Dispatchers.IOне спасает:Thread.sleepтам не заморозит UI, но займёт поток пула, которого может не хватить другим.
Полезно разобрать пример с runBlocking: если внутри него запустить launch { delay(1000) }, а потом вызвать Thread.sleep(1000), корутина даже не стартует, пока поток спит. runBlocking крутит события в одном потоке, и sleep занимает этот единственный поток.
Где спотыкаются
Ставят Thread.sleep в корутине на Dispatchers.Main «на пару секунд для анимации». Экран замирает, а при долгой паузе система показывает ANR.
Думают, что delay создаёт фоновый поток. Он вообще не создаёт потоков, ожиданием занимается планировщик диспетчера.
Оборачивают delay в try/catch (e: Exception) и глотают CancellationException. В цикле это превращается в холостую прокрутку: отменённая корутина снова и снова ловит исключение и не завершается.