Как правильно завершать корутины

kotlin

Статья о том, как в Kotlin устро­ено завер­шение корутин.

РЕКОМЕНДУЕМ:

Нач­нем с того, что интерфейс Job, пред­став­ляющий корути­ну, име­ет метод cancel(), пред­назна­чен­ный для завер­шения корути­ны. Его вызов при­водит к сле­дующе­му:

  • Ко­рути­на завер­шает­ся на пер­вой точ­ке оста­нов­ки, то есть ког­да про­исхо­дит вызов какой‑то стан­дар­тной suspend-фун­кции из стан­дар­тной биб­лиоте­ки корутин (в слу­чае с при­мером ниже эта точ­ка — метод delay()).
  • Ес­ли Job име­ет потом­ков, то они тоже будут завер­шены.
  • Job будет завер­шена, она боль­ше не может быть исполь­зована для запус­ка новых корутин (сос­тояние Cancelled).

Как и в этом при­мере, зачас­тую пос­ле job.cancel() сле­дует исполь­зовать job.join(), что­бы дож­дать­ся фак­тичес­кого завер­шения. Это нас­толь­ко час­тая пот­ребность, что биб­лиоте­ка под­дер­жки корутин вклю­чает в себя фун­кцию‑рас­ширение cancelAndJoin().

Ког­да Job получа­ет сиг­нал завер­шения, она меня­ет свое сос­тояние на Cancelling. Затем, при перехо­де в сле­дующую точ­ку оста­нов­ки, она выб­расыва­ет исклю­чение CancellationException. Это исклю­чение мож­но перех­ватить, но, что­бы избе­жать труд­ноуло­вимых багов, его луч­ше сра­зу выб­росить сно­ва:

Бла­года­ря тому что завер­шение корути­ны при­водит к выб­росу исклю­чения, мы можем исполь­зовать блок finally, что­бы кор­рек­тно зак­рыть все исполь­зуемые корути­ной ресур­сы (базы дан­ных, фай­лы, сетевые соеди­нения). Одна­ко мы уже не смо­жем в этом бло­ке запус­тить дру­гие корути­ны или вызывать suspend-фун­кции. Эти дей­ствия будут зап­рещены пос­ле перехо­да корути­ны в сос­тояние Cancelling. Единс­твен­ный выход из этой ситу­ации — исполь­зовать блок withContext(NonCancellable), который поз­волит выз­вать suspend-фун­кции, но не будет реаги­ровать на сиг­нал завер­шения:

Еще один спо­соб выпол­нить код пос­ле завер­шения корути­ны — это метод invokeOnCompletion(), который будет выз­ван незави­симо от того, как была завер­шена корути­на — при­нуди­тель­но или она прос­то отра­бота­ла свое вре­мя.

За­вер­шение корути­ны про­исхо­дит в точ­ках оста­нов­ки. Но что делать, если в коде корути­ны нет точек оста­нов­ки (нет вызовов suspend-фун­кций)?

Один из вари­антов — исполь­зовать фун­кцию yield(). Эта suspend-фун­кция при­оста­новит корути­ну и тут же ее возоб­новит, но по пути обра­бота­ет сиг­нал завер­шения. Вто­рой вари­ант: Boolean-поле isActive, дос­таточ­но прос­то про­верить его зна­чение и, если оно рав­но false, закон­чить работу внут­ри корути­ны. Еще один вари­ант — выз­вать фун­кцию ensureActive(). Она выб­росит исклю­чение CancellationException, если корути­на уже получи­ла сиг­нал завер­шения.

Cancellation in Kotlin Coroutines

Понравилась статья? Поделиться с друзьями:
Добавить комментарий