Как управлять жизненным циклом корутины в Kotlin
Что проверяют
Понимаете ли вы структурированную конкурентность: у каждой корутины есть родитель, и отмена идёт по дереву. Следом обычно спрашивают, почему корутина не остановилась после cancel() и куда пропало исключение из async.
Как отвечать
Каркас:
Job:launchвозвращаетJob,asyncвозвращаетDeferred, который тожеJob. Родитель ждёт всех детей, отмена родителя отменяет детей;- готовые скоупы Android:
viewModelScopeотменяется вonCleared(),lifecycleScopeпри уничтожении владельца. Во фрагменте для работы с view беритеviewLifecycleOwner.lifecycleScope. СборFlowдля UI оборачивайте вrepeatOnLifecycle(Lifecycle.State.STARTED): в фоне сбор останавливается, при возврате запускается заново.; - отмена кооперативная:
cancel()только помечает корутину отменённой. Функцииkotlinx.coroutinesвродеdelayиwithContextэто проверяют, а свой вычислительный цикл должен зватьensureActive()илиyield(). Блокирующий Java-вызов отмена не прервёт; CancellationException: механизм отмены, его нельзя глотать.catch (e: Exception)ловит и его, поэтому такое исключение пробрасывают дальше. Очистку вfinallyс suspend-вызовами делают внутриwithContext(NonCancellable);JobпротивSupervisorJob: при обычномJobпадение одного ребёнка отменяет родителя и соседей, приSupervisorJobсоседи продолжают работу;- ошибки:
CoroutineExceptionHandlerсрабатывает у корневых корутин, запущенных черезlaunch, и у прямых детейSupervisorJob. Вasyncисключение приходит при вызовеawait().
Ручной Job с cancel() в onDestroy() повторяет то, что уже делают готовые скоупы.
Где спотыкаются
Запускают работу в GlobalScope или в скоупе, созданном и забытом внутри репозитория. Её никто не отменит, и она переживёт экран.
Глотают CancellationException в общем catch. Корутина продолжает работать после отмены, а родитель ждёт её завершения.
Вызывают cancel() на Job самого скоупа и удивляются, что новые launch в нём не стартуют. Для остановки текущих задач без закрытия скоупа есть coroutineContext.cancelChildren().