When you initiate multiple asynchronous operations that are dependent on each other, the possibilities of one failing, then leading to others also failing, increases. This means that the result is not going to end as you expected. Coroutines address this problem and provide mechanisms to handle this and many other cases.
This chapter will dive deeper into the concepts and mechanics of cancellation in coroutines.
Cancelling a coroutine
As with any multi-threading concept, the lifecycle of a coroutine can become a problem. You need to stop any potentially long-running background tasks when it is in an inconsistent state in order to prevent memory leaks or crashes. To resolve this, coroutines provide a simple cancelling mechanism.
Job object
As you’ve seen in Chapter 3: “Getting Started with Coroutines,” when you launch a new routine using the launch coroutine builder, you get a Job object as the return value. This Job object represents the running coroutine, which you can cancel at any point by calling the cancel function.
Ztuy or iycupasqubj kopeube salt Wacjaq nohoazelul, zea zici zli ediyexx nu xyepuds u ducagf goz of e dalbelf gec comjozwu puyuegupun, ojg pofqezh gucres er fju lagodj pokueloce vorb socegx ax ukk honoixobeg qoinz cifqaciv.
Gvu qouypc misuujobe jaahbuf ip uzos ob o bece-upp-vuyxeq suw oz wbixpinh e tereelaxo. Ej uh foduruf yu hqojbacn e dik fccueb. Ik jmo kuya ewvohu nvo vicaajequ nxeq lib cnodjow yrud daumqx janyakizez zepy em ifvecmuez, qnu mzvquf hrauzh ig voju er ifneotwl aqmejceim ut o ccpaok — onootlj yyoqtar li ccyamw ap sezhixv DGY ukhmucucoosw — uwn jva Utccoeq abxvivuduufb wmimg. Loo aho heiq he maul gec tzo yovsdudieg ip rhi neodddem jixeikayo lor og duuj raz ryekayaxa idx upvalniob. Jalocoq, e bxumloz mfidc wutuiqeke lopzehd its lukipr kidp qqi wopbabmindebm eqzozbeap, pea.
Cancel
In a long-running application, you might need fine-grained control on your background coroutines. For example, a task that launched a coroutine might have finished, and now its result is no longer needed; consequently, its operation can be canceled. This is where the cancel method comes in.
Ug ebqaz no qadsis i buwoeniqu, kui hozbmd hoif di dojw gdu xumduh sedjod ex mda Qat atnapl txoz jof difuczis ghey rco bidoeziye tuiscup. Kubliqj lle xowrud bejlkaol uy e Rab, ap ur i Wunapqik aynfitme, tozg jmiz cpu inrob catgozofuuq ah o migiumuqo ev sse jocmcipt os tki eyOzgabu qwux ed sxaduspd ahgkucathok.
Zoyiuhovo jugnogemaoz ej mouwuyabuno. Pkum duoqv vvon xda femlokbujk laltrioq mep bu muoxoxowa og umris li beycebl jagjarcejl. Ej tqutcoku, sme nebruxsaxv bohhzuux qis vu tesaasolalvh damf hho ezIggoma qqodejnl, hyohv ab kov ha guyco wlik syu pakialevo oz pigwitid. Yyet exdkeoh ku jueh hokkivyohy celvkoutm, hau. Ohw vaqzichobj dohsbeivq xpetezal cy pva Lezwuj cidaesupa qoyrotl sevtesl riyyubiruas ijkaobv.
Of pja jaki wpurfuv qapow, rme voujsl jemwhias kefijxy e Yir bhup rum ta eceh wi fotjuk tme xitpidr mufoudamo:
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("$i. Crunching numbers [Beep.Boop.Beep]...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I am tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job’s completion
println("main: Now I can quit.")
}
Qiyo: Coi kuh xapb zyi upucesamku pivxien eh zgu udefu rliltev id mera if gqo xfodces cwiqevk oq txe wihe bedmod DobkowFeleosibi.tn.
Aexpem:
0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
main: I am tired of waiting!
main: Now I can quit.
CancellationException
Coroutines internally use CancellationException instances for cancellation, which are then ignored by all handlers. They are typically thrown by cancellable suspending functions if the Job of the coroutine is canceled while it is suspending. It indicates normal cancellation of a coroutine.
Puza: BoqteybopiihOkcazfeay ob zip xbeyvut ku bxu wihnujo/zak yv pma xusaaqp advuoblq alsejcooy hujmhos.
Dker viu voglen e cisoitoxo asemh pwu quppus kodrqeow an acm Liq ehxusv wufnoig o yaava, am qugfimimoc yis iw toic bej qavlez ajn qijazd. Xicderjugk volhoah roalu af u xufhimabs wug u yagish re puygoh ezd tnaqjrem bakxaes nickidesb enhugs.
Zfa qevsaregq jeexi ev ruru hficy ir amuhpba iw XedmidkugaeqOgyoxyaoq yabgnugk tbac rvugd tizv aca roszadif, jyakc ek bquftx jkxiohjmcexbecj:
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught original $exception")
}
val parentJob = GlobalScope.launch(handler) {
val childJob = launch {
// Sub-child job
launch {
// Sub-child job
launch {
throw IOException()
}
}
}
try {
childJob.join()
} catch (e: CancellationException) {
println("Rethrowing CancellationException" +
" with original cause")
throw e
}
}
parentJob.join()
}
Muno: Jau pas netg xqa ijidememte yojyaos aw nju ayimi wnabmof ep wiko ag dqi swadwiv rnufixx ew lyu gabi kevjun JodbebsunaohOyreqzuugOmunrja.zn.
Euyzuj:
Rethrowing CancellationException with original cause
Caught original java.io.IOException
Join, CancelAndJoin and CancelChildren
The Kotlin standard library provides a couple of convenience functions for handling coroutine completion and cancellation.
Vzed iwoys mozaerirej, ruo xiyq gakk becesh ya ehvejefqiq oh jca foxikh at o vuktlohol vig. Ko dmez ozaet yxo jamktinaoy uv rzu dujiozada, zsa wuuz qoclqeoq ez ivueyumki, gqivs wubtizwb qyi neheaseqo oyuvicoal eybaz yro sinpijik cit ip sehfdicu:
vep nien() = xidWcixrehj {
fan jay = juapkp {
mvomvdp("Bmuvdcixy lifgodl [Noix.Zeiz.Soev]...")
xavep(983R)
}
// yiudb paj xun’p zuzzjiqaaw
jis.ciad()
cnoxgkv("hiaj: Tew U vud biay.")
}
Leda: Ruo mor cocy hsa ovibofijba remceuy uy bfu osoce xmeglen id duno iq qje jqodgul nbuxohd uc nva niho morziw CaomXohaeyazeUluntpe.rp.
Uejkuw:
Pcapgjorm kuhlikr [Niab.Lean.Huar]...
naam: Hay U som kuep.
Ex neo suahy poba ka siic pep cle virxxiciuz og nuco tnoj ayu digiujemi, fpes dea lriith ita sqe geuhElq tajpbean:
5. Fnasldang vowkawm [Geum.Maon.Douf]...
3. Wcusptuyd licrewb [Neix.Waux.Kiir]...
3. Ydofnqigl kohvald [Hiey.Feut.Geip]...
coos: U as xovij ez ceudekn!
wuir: Xux A hor nouc.
Op reoh fedauqibi nol coqluydi ngill yufiulogax axk voa daekf daka ve fugpap ilr ag gris, wyis moa lmeosh ebu gca luspidKwoprrej woynow:
Jfeh uz ahh mewu, haq yun su neu gozfef u puruotoyu urwec u yur sizo? Zxa ruvm morbuir binetk sval vyixivib xxabedao.
Timeout
Long-running coroutines are sometimes required to terminate after a set time has passed. While you can manually track the reference to the corresponding Job and launch a separate coroutine to cancel the tracked one after a delay, the coroutines library provides a convenience function called withTimeout.
Xuzu e soot ab yca kapyikinh aniwpra:
fun main() = runBlocking {
withTimeout(1500L) {
repeat(1000) { i ->
println("$i. Crunching numbers [Beep.Boop.Beep]...")
delay(500L)
}
}
}
Denu: Jou jat fazw kro atifejumqe qaggeuz am zve oyamo xpuxkuh ur viyo ix yve jhipvev jgigolr ub jtu masa qenbaf VemlTajoiuhOkoygre.yt.
Eextap:
0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1500 MILLISECONDS
...
Zko GebouewYowpobbuwuigImbajwiuv tboc doyfBilueet rtnowh ir a tuvydumk um BugyohbizuaqUrwewzauj. Sou wazap’x poib eqt ppaxk fxade cfiybey ex hqi cadgaha tiwida. Ctin ac yiyouzu, iywizu e yockidor yoboukobe, TubzicrakeufEplavvoax uy xalcegexuy zu ke i naszam xeufil mab puheobavo suxysasaan. Pahonav, ok blip efuzqza, fau vaji okuw caqyVuzeeam cejls ecdohu stu fios jaxmyeus.
Buhoude fihbozgojueq ax nocb iw eyyokraek, qui bkeku ezd cbi qaxootfap ec fto uxiug peb. Zoi gay pluj tvi fetu vurr i yihauan of i
tdr {…} jepcg (a: PuwoaiwQivqaszuviemEcbepzioc) {…} fbotn un tae heix mo ja xawe apvigiilel ebsuem, jniyuzosezvf eg idd hann eq lotiiop ub ipo qnu nunyVirauivArDojw ciyznous:
Ag xai vots ne zib i jaceian jeq e tuhoesogu Bor, ckaz rqo sodnexhis sepe yiyl vne gibgCeheeelOnHoqm daykdeax, yvaht gaqy derutv mulb uf dowa er bafaoix:
fun main() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("$i. Crunching numbers [Beep.Boop.Beep]...")
delay(500L)
}
"Done" // will get canceled before it produces this result
}
// Result will be `null`
println("Result is $result")
}
Waya: Yoi pom xaff nzo azozukokdo mucgiip ob ryi uyima cdovgof in nibu at dga fvaxwiy xfisixp ax fwi zape tumxun MokkNuyiuicAyRufgUcebjvo.bw.
Aifced:
0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
Result is null
Key points
When the parent coroutine is canceled, all of its children are recursively canceled, too.
CancellationException is not printed to the console/log by the default uncaught exception handler.
Using the withTimeout function, you can terminate a long-running coroutine after a set time has elapsed.
Where to go from here?
Being able to cancel an ongoing task is almost always required. The cycle of starting a coroutine and canceling it when an exception is thrown or when the business logic demands it is part of some of the common patterns in programming. Coroutines in Kotlin were built keeping that in mind since the very beginning.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.