Разработчику
Проблемы использования корутин в Kotlin
7 Gotchas When Explore Kotlin Coroutine — статья о проблемах, с которыми можно столкнуться при использовании корутин в Kotlin. Вот наиболее интересные тезисы.
1. runBlocking может подвесить приложение
Рассмотрим следующий код:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) runBlocking(Dispatchers.Main) { Log.d("Track", "${Thread.currentThread()}") Log.d("Track", "$coroutineContext") }}
Выглядит безобидно, но он приведет к фризу приложения. Почему так происходит, подробно описано в статье How runBlocking May Surprise You. Если кратко, то проблема возникает из‑за самого принципа работы runBlocking
. Он запускает новую корутину, а затем блокирует текущий поток. Но если запустить runBlocking
с диспетчером Main
из основного потока, то порожденная корутина окажется в том же потоке и в итоге не сможет получить управление, так как текущий поток будет заблокирован.
По‑хорошему эта проблема решается отказом от использования runBlocking
. Это инструмент для юнит‑тестов, а не для продакшена. Но если очень хочется, можно убрать имя диспетчера из вызова функции:
runBlocking { Log.d("Track", "${Thread.currentThread()}") Log.d("Track", "$coroutineContext")}
2. Корутину нельзя завершить в любой момент
Неопытные разработчики считают, что если вызвать метод cancel(
корутины, то она будет завершена сразу. На самом деле это не так и в ряде случаев корутина может успеть полностью отработать, перед тем как обработает сигнал завершения.
Происходит так потому, что корутины реализуют модель кооперативной многозадачности. Когда одна корутина посылает сигнал завершения другой корутине, последняя может либо обработать этот сигнал, либо проигнорировать его. Хорошая новость состоит в том, что все стандартные suspend-функции (yield(
, delay(
, withContext(
и другие) умеют самостоятельно обрабатывать этот сигнал и завершать корутину. Плохая новость — благодаря такой невидимой автоматизации разработчики бывают удивлены, что одни корутины в их коде завершаются почти мгновенно в ответ на cancel(
, а другие продолжают работать.
Проблему решаем так: проверяем значение свойства isActive
между вычислениями и завершаем корутину, если получили значение false
.
3. Отмененный coroutine scope нельзя использовать повторно
Взгляни на следующий код:
@Testfun testingLaunch() { val scope = MainScope() runBlocking { scope.cancel() scope.launch { try { println("Start Launch 2") delay(200) println("End Launch 2") } catch (e: CancellationException) { println("Cancellation Exception") } }.join() println("Finished") }}
Данный код не будет работать. Если вызвать cancel(
на coroutine scope, он становится непригодным для дальнейшего использования. Выхода из этой ситуации два: создать новый scope на месте старого и завершать не сам scope, а все принадлежащие ему корутины:
resultsScope.coroutineContext.cancelChildren()
Используем колбэки в последовательном коде
Suspending over Views — хорошая статья о том, как превратить колбэки в suspend-функции с помощью suspendCancellableCoroutine(
.
В Android колбэки повсюду, и UI фреймворк не исключение. Колбэки используются для всего подряд:
- AnimatorListener — чтобы запустить код по окончании анимации;
- RecyclerView.OnScrollListener — чтобы выполнить код сразу после промотки RecyclerView;
- View.OnLayoutChangeListener — чтобы узнать, когда View был перерисован после изменения.
В целом колбэки не очень удобны и могут превратить код в трудноперевариваемую кашу (callback hell). Функции‑расширения из библиотеки android-ktx частично решают эту проблему: преобразуют колбэки на основе классов в лямбды (doOnLayout(
вместо OnLayoutChangeListener(
), но всем нам хотелось бы писать код в последовательном стиле, как и предлагает язык Kotlin и suspend-функции. Но можно ли этого достичь, имея дело с фреймворком Android, написанным с помощью колбэков?
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»