Как работает cancelAndJoin в корутинах Kotlin?
Что проверяют
Понимаете ли вы, что отмена в корутинах кооперативная и что cancel() сам по себе ничего не дожидается. Следом обычно просят объяснить, почему корутина с тяжёлым циклом не останавливается после cancelAndJoin(), и что делать с блоком finally.
Как отвечать
cancelAndJoin() это suspend-расширение для Job, которое последовательно вызывает cancel() и join(). Разберите обе части:
cancel(): переводитJobв состояние отмены и сразу возвращает управление. Сама корутина при этом ещё может выполняться;join(): приостанавливает вызывающую корутину, покаJobне завершится полностью, включая дочерние корутины и блокиfinally. Исключение отменённой задачиjoin()наружу не бросает;- кооперативность: отмена срабатывает в точках приостановки библиотечных функций (
delay,yield,withContext), они бросаютCancellationException. Цикл без приостановок нужно проверять самому:isActive,ensureActive()илиyield(). Блокирующий вызов вродеThread.sleepотмену не заметит, для него естьrunInterruptible; - очистка: в
finallyможно освободить ресурсы, но suspend-вызов там в отменённой корутине сразу бросит исключение. Если нужно дописать в базу или отправить событие, оборачивайте код вwithContext(NonCancellable).
Типичный пример на Android: поиск по мере ввода. Перед новым запросом вы вызываете searchJob?.cancelAndJoin(), чтобы старый запрос гарантированно завершился и не записал устаревший результат в StateFlow поверх нового. Для задач в viewModelScope отдельно отменять ничего не нужно: scope отменяется в onCleared().
Где спотыкаются
Думают, что cancel() мгновенно останавливает код. Корутина доработает до ближайшей точки проверки, а вычислительный цикл без ensureActive() доработает до конца.
Ловят CancellationException общим catch (e: Exception) и не пробрасывают дальше. Корутина продолжает работу, а join() ждёт дольше, чем ожидалось.
Пытаются вызвать cancelAndJoin() из обычной функции. Это suspend-функция, из кода вне корутины доступен только cancel().