🔥

Тред (Алексей Панов)


Что-то подустал я уже графоманить в ваших твиттерах, но не могу не рассказать вам о самых частых ошибках при работе с Kotlin Coroutines.

Я начал свое знакомство с корутинами еще 2019 году, тогда нельзя было найти о них много хороших статей и примеров, а оф. документация была написана достаточно сложно и о многом умалчивала. Но даже сейчас, смотря на чужой код, я вижу множество ошибок при работе с ними.

И самой главной проблемой является обработка исключений в корутинах. Возьму слайд из своего доклада, как думаете в каких случаях ваша программа свалится с ошибкой? GetException — это suspend функция, кидающая исключение.
notion image

Правы будет те, кто выбрал первый и третий варианты. Так как исключения в корутинах не пересылаются как в случае с обычными функциями, а распространяются вверх по иерархии Job.
notion image

Если с 1 и 3 вариантом мы разобрались, а во втором случае все просто: suspend функция пересылает исключение подобно обычной функции, то четвертый случай самый неочевидный.

В случае если top level корутина запущена с помощью async, то exception будет инкапсулирован до тех пор пока мы не вызовем метод await. Но на самом деле вряд ли вам придется это использовать, так как дожидаться результата с помощью await мы также должны в coroutine scope.

Как же тогда правильно обрабатывать любые исключения? Способов на самом деле много, но можно использовать CoroutineExceptionHandler, его обязательно нужно установить либо в сам scope, либо в top level корутину!
notion image

Ещё частенько при создании своего скоупа люди указывают Job, за место SupervisorJob. В таком случае даже если у вас проставлен exception handler, при возникновении ошибки, вы больше не сможете запустить корутину в этом скоупе!
notion image

Ещё одной распространенной ошибкой является нарушение Structured Concurrency, если вы хотите запустить дочернюю корутину, не используйте для этого top level scope, используйте специальные scope билдеры!
notion image
notion image

Также не забывайте про main safety, любая вызываемая вами suspend функция должна сама управлять тем, на каком диспетчере она будет выполняться. Так вы всегда безопасно сможете вызвать эту функцию из main потока.

Но при этом, если вы производили какую-то операцию в бэкграунд потоке, не нужно обратно свитчиться на Main dispatcher, все будет происходить автоматом! Это одна из самых распространенных ошибок.
notion image

Обратная ситуация, когда меняют диспетчер при работе с Room или Retrofit, делать этого не нужно! Эти библиотеки сами поменяют диспетчер для запросов в сеть или базу, при этом сделают это более оптимально.

Бесит каждый раз писать кучу бойлерплейта, чтобы показать SnackBar, это решаемо, но все же бесит. pic.twitter.com/KopTBPIbjq
Ну и напоследок, в этом треде я намеренно (на самом деле нет) сделал ошибку при работе с корутинами. Метод showSnackbar в этом примере является suspend функцией. Как думаете в чем здесь проблема? twitter.com/mobileunderhoo…

Это не слишком очевидно, мы пытаемся при возникновении нового эвента убрать предыдущий snackbar, и показать новый. Однако метод showSnackbar приостанавливает вызов onEach пока не будет завершен. Чтобы это исправить нужно запустить дочернюю корутину с помощью launch.

На этом все, как говорится, не болейте и используйте корутины правильно!

Алексей ПановАлексей Панов