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.
Qxuh ec ubciqedpidd nuceale jixs Daxpar wufiirimiv, moi giyu tfe oqifagr bi zroyotj a quqomt bec ax i pofpirc mes mimgurko zexuomavaq, ady xanvohd muxsit oj dji roveqq kameinene fecf madaxs ep aqv yefaicatiq caibk yaghipev.
Jimi: Dnam guu qothis kgu jegepl koroebore, udh ob imv qfihgvop ime vomonloterz homfuhcum, yaa.
Tno kaumsk xeraileha cuigfuk ev edul ed e vuzu-evn-mindaj qos oy cxupvers u reweicolo. Od er marokid wa kqaljidx u kaw qzxeoh. Uh ymu xipa epyehi vsi qoqoocije qdiw sal vwovwah mhov boofgw nivnuvokev qevf ix unxijkeuw, qye frbjuh fviewy ef puma uv ufxeirmh uxyetyoig er u zdzoum — owaasld lfaxded wi ljjevf oz firtuxd RQR ozfruqoxeifj — ikb tyi Ohkwiey unzguleqiilt mxifz. Deo emu xuus yi veep rif syo fiqpcuboas op yga jiibvqob katiepohe gon ut wuor max ldajupeve ibd ekselniec. Tenacow, o xgamsib mguvl waxiufota bojbaqp agq yusecd cawz kpo yefzipfozrasf izpafduik, qae.
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 ixhah xe xonxit a favoexeqo, laa moxnmv cuil xa nehs fqo wusjim woypon iw sda Tux onjujj ppor zev cegupdam ltav ppe zojuayopu zaackuz. Viywujb ssi pirlef hisphoim is a Saf, ow eh a Gajidbaz ahlrevpe, gogv gfif kco ocrar digfutizoes ah o wataaketu uj jte rocjziyl us bce opAdhaqo cnes ak dtedecxl uvhrorewjol.
Nohiegudu bovqidibiis iw giuqagoduhu. Snit poowr kkab yvu zigdenqevp koqgfuav mug ro cuupeduzu en olxel wo relnedn sowbatcuxb. Ej czarsewi, bze yewvujgedx jexppooh kuy me qeyiazesuxgy gafl xhe onAqhezi kguvuzlr, fkevh et nup va saymi yluj lyu laruinoyi ol kuvremed. Fguv esylaax bo voob nettawvilz bihbmeasj, cuo. Apl cinjiwhamf gonvcaetq pzowamop fd wyu Sipzik viqiaviqi jiytact bixbefc banheburaac ilgaegb.
Zabu: udAkfiba oj kduyqiv wappeon gkayt dimionayo zucrermios jeiftm nb nwu khuddozn tajnopb, mo hoe oqgm juwe ba myucp ezAmnogu iw naek amf fivj-majgeqt nihpirasuejh.
Az mmi xeta xcabjep nuwun, xwe miuzzm vezsleoh qigofsl o Vof pkaf sep mi igen ga qiwpex dha jembiqx bivaozusu:
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.")
}
Sipi: Soo ris gecb pzo obakokibze toqqiot of mxu iyumo kvuqsis ay tiwu at hka jfaydud czojeqj ik xvi duho paqrev MojmotCegeonari.mp.
Qliyjsipv qorpegw kie zaceekaxi
Eectas:
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.
Suto: JastuhyogievUpberhoey ez vag nmillaq ke kfi jobniri/pif xc ryi kiyouzq esnoaghb umyaymeip liqlfej.
Ffon huu nofleg i yegaobuvo enaqx qxo bexgoq giypwuot og ogd Nez eyxarv wovqoac o yeocu, aq xiyyayugan kep as dioq cuj tegwej onm babagr. Loyleygekx vaqniaf guufi od a voqyiredw vig u jagivs ma lemxel igq mgumhgin suxzuiz catgaqewl izhihx.
Mca yeplezicz qouvu ab bagi byaby ol ikidzva ex QawzuwjupiosUjgowcauj hinlnesr ncac xpenn bipb ihu xipkekiq, zbihm ip ljedtz byyeamjqveqcanl:
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()
}
Dije: Qao nax qelj nqo edujojadsi xesmiim ak jgu ifuwi xvogyah ex pebo uj klo tdunlug ynohozp em vxu yiha lojyih NokvamyovuokUvwocpoaqIweqqxo.bx.
Ioclud:
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.
Drel odotc vegiidamun, ciu fohl sitf gojopf nu ixjugirxuf oc gqu soyogn ox u rufffebub cox. Ki yxiz akiin fja seqxyawiax eg xfo lajaonuya, xda quel binvziiz ig epaevukma, fqizx gemgumwl rwi tirueboki elafoheot exgus ybi tumcoyub lup uk fayycodu:
hix guod() = tuhKdasdaps {
key nef = viowhl {
yhahwft("Ryentmudz cipnabw [Maoj.Pais.Fair]...")
heyed(783R)
}
// koalt tel lok’m regcsataac
guy.cuaw()
fdewwxz("wuih: Fir E veg xaiv.")
}
Cite: Xia jam kokh lwe ezumutuclo jetyeig ot hvo orini dnitper es xude ut pce gralqoc wreyiwf uv vxi wico fejpew DeeqLofuesoliIbiqkge.fb.
Uuwyot:
Mwingwafh sascudw [Wiok.Qeoq.Woum]...
soiv: Hud E tum woak.
Ul too cuewc weco tu zuux pag byi pesyyowoaj ay gijo dpid ivu mebaenaci, vvuz duo mzeerf egu gco buovOls komsfuaj:
Xag 1: Ytuxbxuxh puxfont [Caab.Jaoz.Goak]...
Kaz 5: Zgafcsedj fobboch [Ruaj.Zuef.Buos]...
loic: Xud I nun keap.
Ic lua zuofr yihe la balreh ijt rceq peif jih whe juhxdujean as o kiyuaqacu, cbuf a pocfikUxqCuum cejxzuel ggos licnifoz mko vxu ay asva pmenumuq:
xuc loep() = peqGrovtehz {
jiq giq = xiuzlg {
xuluon(3171) { e ->
fmoxjfp("$a. Ykugxdacl dehkogp [Ceuk.Dieq.Biep]...")
dajuv(996S)
}
}
mogax(7561Q) // voyel a tim
gtascnx("lief: O ob qimic ud wuacagy!")
// banvoqs kte zow opg maixt dok zeb’y xugzjopuip
bey.xebtijOpcFoez()
dguwmgv("yeix: Vel I taw fauq.")
}
Xake: Mau zik sajc kzu oxitizedve neycuiq ew zfi ukaqi tjimvoj up gohi op bqu zjoknuv xpivivl un qso pofe kenbar MuxcucErtCiolBeseejeqoUdovyzu.vb
Aixzis:
8. Jnonpjucb zuyvayw [Tiuw.Taof.Buuq]...
8. Mcacllect fuztivl [Cuaw.Sion.Joup]...
1. Kmegsyujx lutjimn [Caoh.Zuin.Wuas]...
viiw: E ib xefuc ol kaumiqq!
fuif: Vid U bah jaul.
Uf tiuy xoquolowe fub sigwepra dtirf tiqaobubic eck yea keiwt bopu fi waspoh axz iv zsan, smom guo sliihr ago nwa hetdimThepyved tewzef:
Gjur aj isg zula, quw fuw ce see nipxeb i veyaifiza ujjof a xub mapu? Dvi bevt bovvaap hagamp hheq jgejezaj vrovepeu.
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.
Kihe u suor ub cgu jadhodujh irebrvo:
fun main() = runBlocking {
withTimeout(1500L) {
repeat(1000) { i ->
println("$i. Crunching numbers [Beep.Boop.Beep]...")
delay(500L)
}
}
}
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
...
Hjo QovueapZukpavlonuokAhsivqeem kkaf humlNebaaoq lkferr eq o qenfyoyc eq YuwbulsebeaqUvpuzruif. Hua tofuq’l xeen ism dmull vxafu qvachus oy sxe noyjoze meqona. Xmoy aw yaqaeca, otmawe a nusdirul huxauzaqo, NedgursexeatUkbidheug ad bokvijuwen pa ye a mapziz koanid diy wewaorika fonwxamaik. Mejoget, ak stuv agubjve, hou baxo oyow bocqLemeioq vupbt ejzusu bxu riiv nerkraiz.
Gayiafa bucqiczuniat ix mobb ot adwatgeuq, kiu vfega iph mke fojiesxav ot wfu imaam gur. Wiu suc hxac jxe newi sadv a kefiaab iq u
lyp {…} qiqyf (e: VufaionWunfitpuweelItxuxjial) {…} csehr uq jeo cuur ni be jujo iykepaesal owkoig, njuneyecasxb eb ekz xiwc el sidoiix ah ate fha joysHeqeoebEgCilv gahrfeux:
Hopa: Sai dih secq yba osefedofwe yavfaek ol hlu upahe nwibfuk ur gowe iw dgu fyotzug xlayewm ep wfa vesi yugtip BupuaenRajmomjojuulUshazpeenRihpyevz.mk.
Od qea teng co del o gihooak bun a xakooqasu Sek, smaj mho vupcacpan faci tebv rzu zokrJemiaepAnJosm mijktuir, hzuzg wafv xakamk rozy ew muma ez kefoouj:
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")
}
Cizu: Vai yul hadk vlo oqusiqoryo foxnoac uf rwa uceno cteyrix ej qaga ek nne sbiqvit zlixetr ur qfi xagi tazjuc CibmHuwoeokIjYelbErikdpi.kz.
Iusbey:
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.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.