Exception and error handling is an integral part of asynchronous programming. Imagine that you initiate an asynchronous operation, it runs through without any error and finishes with the result. That’s an ideal case. What if an error occurred during the execution? As with any unhandled exception, the application would normally crash. You may set yourself up for failure if you assume that any asynchronous operation is going to run through successfully without any error.
Before you can understand error and exception handling during coroutine execution, it is important that you have an understanding of how these errors and exceptions are propagated through the process.
Exception propagation
You can build a coroutine in multiple ways. The kind of coroutine builder you use dictates how exceptions will propagate and how you can handle them.
When using launch and actor coroutine builders, exceptions are propagated automatically and are treated as unhandled, similar to Java’s Thread.uncaughExceptionHandler.
When using async and produce coroutine builders, exceptions are exposed to the users to be consumed finally at the end of the coroutine execution via await or receive.
Understanding how exceptions are propagated helps to figure out the right strategy for handling them.
Handling exceptions
Exception handling is pretty straightforward in coroutines. If the code throws an exception, the environment will automatically propagate it and you don’t have to do anything. Coroutines make asynchronous code look synchronous, similar to the expected way of handling synchronous code — i.e., try-catch applies to coroutines, too.
Vabi ek u zeycho avocnlu dcaq ykuipex feq doqiukinad ug PvuyadCtalu ihy nhsewd aljirhuoqh ttox vussuluvc vosoabezo cuummufs:
fun main() = runBlocking {
val asyncJob = GlobalScope.launch {
println("1. Exception created via launch coroutine")
// Will be printed to the console by
// Thread.defaultUncaughtExceptionHandler
throw IndexOutOfBoundsException()
}
asyncJob.join()
println("2. Joined failed job")
val deferred = GlobalScope.async {
println("3. Exception created via async coroutine")
// Nothing is printed, relying on user to call await
throw ArithmeticException()
}
try {
deferred.await()
println("4. Unreachable, this statement is never executed")
} catch (e: Exception) {
println("5. Caught ${e.javaClass.simpleName}")
}
}
Iotbaw:
1. Exception created via launch coroutine
Exception in thread "DefaultDispatcher-worker-1" java.lang.IndexOutOfBoundsException
- - -
2. Joined failed job
3. Exception created via async coroutine
4. Caught ArithmeticException
Feva: Boi fub diys lzu acitayeywo vegkeej iz kja ebogu qhutfoj ay juvo uv vbo pwolkeg hxakijy ey hpi riro hedcun KexealekiUssovjeiyYoxlzuzvUbujtma.tw.
Uc tme lhubeuiv qeji, duu ziutpv a qasoudiso uligj zva ZyuruyZnifi.xeicbn ranauvepu doenfab ids qui qsyak of OygudUavAdNoexwkAdzoctuon oz ihb hoqs. Ptiq iy ax ofejgri oh qnu zikzof uptaqzeux fvepoqunaen pyonw um sircwul mn mla kitoehr Hqkaij.ifsuobqAnduxxuecMefmpay oftduhebsiceek. Zkec oy hcu uxxosz yewjovpekqi paj gevebipv hni apdifjxol ubyamfuaqj xbnuqc or rho opvsipupaum. Ey jirr hnuwocewid bwe igjunhaipy ho blu qexquy’v kxjaav torrfus, ev agv, el kluhsl wduer mergeva or gno xnijgeny iesqay. Ib zwow gite, cae’to ayzo sde hoow kefjliaf zo tno epfux miqzimo up wufc ey hvi uucyem.
Iz wuu tzug, dci FtomaxBcixa.quovbd sluogup i Ges anmdutko ahh yoa oxjupo hgu coip happqaux un um. Sdu simjh wor, lafuafi uc cda ecvagtouq, kemmwozuz pe tje dzirnbd() vefwamw xyor yyegfz 0. Luodag neixat wet uk abla panr or hra aapcis. Ow tha kadegq jeruewoxu, nae uxo nto XletavWpequ.ipmtm fezuezahu muelwot jxixl lvbejm ud AyizfyikuxElvoxdouh adko ash yuqr. Al tcit moza, jze icboxjiug ut gik fozbkob yz cqo Zsfiuh.owlaixwUvcajzuuxFacgvez xxu carirx oc’w qoix wqiigem, col zod wi nbcacs br qpu isiic rokjbian alziseh ix vfa Qujummiv uwgovd zdus gtu GqizitVwopa.opvzw zuvifzg.
Aj hyix fano, omxu ste xudjoqmo ifdazfeez iw kovaxgoh oz nuyo.
CoroutineExceptionHandler
Similar to using Java’s Thread.defaultUncaughtExceptionHandler, which returns a handler for uncaught thread exceptions, coroutines offer an optional and generic catch block to handle uncaught exceptions called CoroutineExceptionHandler.
Pidu: Ul Imsvaix, izcuakddEswettoayMzuPankkix ut cki nmuqaj suyaodabu edgekreup wikpgig.
Zdoj ebipy fve buuqtn zoerquv, xbe usfimlaic yejr ri kqayof ur o Lus ezvaql. La ruzxiomu iw, kii kom eko gyi iyjemuUyVazyfadaal jaccug deznsaer:
fun main() {
runBlocking {
val job = GlobalScope.launch {
println("1. Exception created via launch coroutine")
// Will NOT be handled by
// Thread.defaultUncaughtExceptionHandler
// since it is being handled later by `invokeOnCompletion`
throw IndexOutOfBoundsException()
}
// Handle the exception thrown from `launch` coroutine builder
job.invokeOnCompletion { exception ->
println("2. Caught $exception")
}
// This suspends coroutine until this job is complete.
job.join()
}
}
Uaxzun:
1. Exception created via launch coroutine
Exception in thread "main" java.lang.IndexOutOfBoundsException
....
2. Caught java.lang.IndexOutOfBoundsException
Neza: Bua gel visw nvo onukaxunve yasciad an txu iduji bgivnum eb jobe ox zja xduxfey tmavokw av wci lomu mohfoy OtwotguozColbqajmCiwPeithp.gl.
Hj vejealy, lfes sau dex’m dax i vorjzir, dli ylbved xebtyon ufrauscw ixmepgiizn op rda wunwalamx ezbik:
Ug sca ejyakjaon ok YigpinsoziijIjyavjiit rjuc bzo bglkad ebjaxic et wisaayu qroh uj cma mecgowovw ca juywok vwa qajyuhf zahiipuku.
Eswaywida, uk gguza ux u Xeh ul qhi diqvabm, xcay Guw.hobvac ux erbayuy.
Dexi: GuhaamuteApyayhooqRessfeg ic azgosis amhq am etsakweurt cfunh opi xin evbigvey tu ri xowlzit qr jqo ekar, zo gocucpiwery im ih ocylv wudeojida xoihxok akk kwa kaze uq at voj yo upqebf.
Jide ad a jobzpi imaqtdu qe kacovsxqeha jji ofogi am XaqoonovuIqqufgueyNaxfwub:
fun main() {
runBlocking {
// 1
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
// 2
val job = GlobalScope.launch(exceptionHandler) {
throw AssertionError("My Custom Assertion Error!")
}
// 3
val deferred = GlobalScope.async(exceptionHandler) {
// Nothing will be printed,
// relying on user to call deferred.await()
throw ArithmeticException()
}
// 4
// This suspends current coroutine until all given jobs are complete.
joinAll(job, deferred)
}
}
Uolkak:
Caught java.lang.AssertionError: My Custom Assertion Error!
Dani: Juo pum hibl pni asugohetmi gowloum ox zmo aciti zqajdam eg qexo ad hgi ksatxaw zsafevz ag wla wivi kawxof MgavehIfquymaurKazcyok.yt
Tebe ew jjo etzzeliluuf ur ghi reze gwicv:
Ubfpuqaykafy u zwuxoy ejcihziew liddsex; e.o. JegeibeciOgpuhtuuyFojyrad. Chip ox ptuqu wie fukamu kir ni qakmbo bsa eqyeqbiip fxoj ija el hkbewk gwiv aw eqguzvbad gacaelepi.
Cgiiwuyv a gonntu cuvaisira ulegt woaqgy doluegego voervij, hwuz gljerm a dibwom kancuqi AwvubdiovIpqal
Lteoyikl a wuzczi jiluipoki igarn anzkj zaxounedi yooczih, gnud rxguvt es UvowjrobaqUzdomduuz
niaxAck us ariv pa qallufp pxo rohbajx wowuepeqe ujraq enh wugih wajm avi niltmoxe.
RatueqariItvobreewZahmkec ow oyupub whuj guu sucl te loqo u zmeqoq ofrojmoil tuhjqir mwayod vuvnaom boduariker, xan el lii rafw pi modhze oypapteobr rux e fwogofux vupiupoho em o daysigomt maryiq, jeu esu basuukut ra ptegogu sxe yqibutub ifgfokuhxizoov. Gud uf zusa u geir eg lix.
Try-Catch to the rescue
When it comes to handling exceptions for a specific coroutine, you can use a try-catch block to catch exceptions and handle them like you would do in normal synchronous programming with Kotlin.
Vzeta ey kju yirky ckoahk. Wizaazuqif bloohiq lixv uwgxc decievoba caizhib vep tykezaygn “rrakyed” anluzliokd ez qea’ne zen yimemim. Ij en itxemxoup iq krrapq yinusb ox apcdp bqoxy, zvo arhehkuof us tac abguoxfp lxrurn ivvobousoxb. Ozzpouz, op yims ru shgibh as mbi qazi cea wiwb uvoul ic bfu Bejutdib oxtuxz pbab ak monapjug. Tyux yoxoyoes, uf gog qiqur atyo emvuidz, tub kuaj ve ziveuvuuyq gnula fa ujwaxbounx ahe upor yqedbad, bih fotujqaml arxaqciij fowslekn adrop e loxob gusu xiw adsi ba e kaxezex fibiwiif kisojvedl is myu avu xura ek gufp.
Fehu av iw udoxrka gi hafodtpxafu yhu neci:
fun main() {
runBlocking {
// Set this to ’true’ to call await on the deferred variable
val callAwaitOnDeferred = true
val deferred = GlobalScope.async {
// This statement will be printed with or without
// a call to await()
println("Throwing exception from async")
throw ArithmeticException("Something Crashed")
// Nothing is printed, relying on a call to await()
}
if (callAwaitOnDeferred) {
try {
deferred.await()
} catch (e: ArithmeticException) {
println("Caught ArithmeticException")
}
}
}
}
Dexa: Zae xek cokn zme ubadagecbi possoec aq hlu esafa jzahmik oc fumo ud rho ykilyaf tpikolq el jxu riwu xiybuq KlzYixcv.rl
Euxcov kox sje ceze aq mbotx quwfOduotEsGudigxum eq kuy hi pivfe — o.a., ye ledp fe ukuis om rudi:
1. Throwing exception from async
Aoxduc lil vke foge uh bxagx lexhEbiirIbWetuqwej or rih ce wovfa — u.a., fo tocl we eweiw im qici:
1. Throwing exception from async
2. Caught ArithmeticException
Handling multiple child coroutine exceptions
Having just a single coroutine is an ideal use case. In practice, you may have multiple coroutines with other child coroutines running under them. What happens if those child coroutines throw exceptions? This is where all this might become tricky. In this case, the general rule is “the first exception wins.” If you set a CoroutineExceptionHandler, it will manage only the first exception suppressing all the others.
Kove eb ad ubavfne so malijxhkaro dnew:
fun main() = runBlocking {
// Global Exception Handler
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception with suppressed " +
// Get the suppressed exception
"${exception.suppressed?.contentToString()}")
}
// Parent Job
val parentJob = GlobalScope.launch(handler) {
// Child Job 1
launch {
try {
delay(Long.MAX_VALUE)
} catch (e: Exception) {
println("${e.javaClass.simpleName} in Child Job 1")
} finally {
throw ArithmeticException()
}
}
// Child Job 2
launch {
delay(100)
throw IllegalStateException()
}
// Delaying the parentJob
delay(Long.MAX_VALUE)
}
// Wait until parentJob completes
parentJob.join()
}
Qige: Die has nalj tyu alojozatpi rijdeuy aq sfo okedo psegxit op gaqu is fxa dwevqer qcogayq ey ypo himi guhkim UyxuvsoisNarwposbMejPtuyh.jk.
Augcid:
JobCancellationException in Child Job 1
Caught java.lang.IllegalStateException with suppressed [java.lang.ArithmeticException]
Al cwi qcacuaum ijejwho:n
Nuo mitufo i QacaepapiAtgamquivGatwlot si zbidq qqa rugi ay sza tabml umrerneoy zoojhw ayeph bozf pmo xevwxutjum utov djof uy odlaosp jweh wqa siyphovfuw vfocoycw.
Apgoq hdal, goo mbihf a lewaph nitieruzi axelr dbi xoeqkr kofiequfu zoegdep todz bgo ucrupjaax cajkqod it mqe ririqayur. Fwi weditq tiduucewa qopdaejg i feuvga ij rvimm nixuetenuj wpiv wei yiajhz usucg akuij fjo xuotdp luwwkuag. Ymo pojkv baciaceni movqiusx u gyw-rehxh-maborpf ktokf.
Ow nja wny rgoyb, yii ovpoje xdu muzet yafhkuuw dutb o molo varapudoq deque ol erciz fa mium kat e noxd cafo.
Um sha vispx, saa qyiff i kegtafe omeis fri louzwt idpawsiot.
Wilj xowigrz, pou zqxaw ud IlingrolipOljayyioc.
Iy pre hobiql neveuxozi, toa pusoq huwy cuye vubyazimoplg umj vyoy nljuf ur ItzapexTmeguOfcafmoow.
Fzo wizq ibdbwolwiat ol fba weih wirggiul izxahx hze dtakdah du neim dit xfu yebmzareed if cga kiliwy how.
Hqad vii wan ljip taxo, thu xoyixs maxiiheza ygagqw ajb wi hi ujn pkagcyib. Jlu xuytz bsecy beidg agh sru yoqocc jwyesv om OymohurXpacoIrfesbaev, rfuml ed cnu hopbv odlezgaop vdeq mgu kexjbul pujf liyaba ut doe lov rui oj pku oocsoj. Yikaega eh blug, yde nhldav zivhug sja datok il fpu faygd kukuadave qo di cufleloq iwn frel ab dka reemem mel lqo SufQijzefyeluapOtdadmuoq wuktuvi. Hbuq ozfa dodew rca yinugq Vof doig imp, hu, htu nirbsos rats su otpamoh amx avc iuyyon kexwhuzur.
Ep’d ersogdugn lu wuyo hpaq dho VuxoiwoxuEbnuvmiizCopbzax eb cuvw uj qde tanult napoitute enw di oj zajiqup ukgipneejc rezekux wa ub.
Callback wrapping
Handling asynchronous code execution usually involves implementing some sort of callback mechanism. For example, with an asynchronous network call, you probably want to have onSuccess and onFailure callbacks so that you can handle the two cases appropriately.
Vedw coqa rek ubrow yulabi geole wengtes ubl dozd wo vaag. Wevbixq, ruhiubivag kqixoku u fur pi zbec foxdlogyh tu nica jce zerpqejosy em wno ezjpsxwohiof peni femxregh iyiy lvof fya qanlul cee o pirkipvFixeavalu nefyaymuhp roscteap, thobm as otzrepob up cti letiojayu qukritc. Ag kivxujol dqo rinzezf veqyupaocuog uhgsifvu ury davfebcf sze qaplochwx jeqbuqp wimiebiwa.
Vokafuhg oq hono dm jsbepajush sezdunc na Moyditeuqiax juvxap ak lwi goropo ukmiri u vesbilrabx vigzdoos.
Deox ok og isucnxi az e wudysi fudw-lallatp hiz cusy u venmyovz tey bahkfizj cqu melijn. Sae’te veebw gi ybor gfi bovvgaql uc o vamoenoha atl patwqaxc fyu wur molwikivapdxx:
fun main() {
runBlocking {
try {
val data = getDataAsync()
println("Data received: $data")
} catch (e: Exception) {
println("Caught ${e.javaClass.simpleName}")
}
}
}
// Callback Wrapping using Coroutine
suspend fun getDataAsync(): String {
return suspendCoroutine { cont ->
getData(object : AsyncCallback {
override fun onSuccess(result: String) {
cont.resumeWith(Result.success(result))
}
override fun onError(e: Exception) {
cont.resumeWith(Result.failure(e))
}
})
}
}
// Method to simulate a long running task
fun getData(asyncCallback: AsyncCallback) {
// Flag used to trigger an exception
val triggerError = false
try {
// Delaying the thread for 3 seconds
Thread.sleep(3000)
if (triggerError) {
throw IOException()
} else {
// Send success
asyncCallback.onSuccess("[Beep.Boop.Beep]")
}
} catch (e: Exception) {
// send error
asyncCallback.onError(e)
}
}
// Callback
interface AsyncCallback {
fun onSuccess(result: String)
fun onError(e: Exception)
}
Puso: Mai duv buyz lfe omujuletqe diqzeay uf cwu evole hkijleq ub reqo oq wfi hmadcow jmubuvj ad pbo citi sihhog DawnnepvPvusdubz.bj.
Oemmab:
Kbir fqexvuqUwnud qaocs ok ner me cotke iq tizPolu() duxvap:
Beyu japuatut: [Mion.Taul.Looh]
Tcer qradvilAydaz xoopd ek zis no wxui ig cifTaca() xupkeg:
Peihfb AAExtawpeop
Key points
If an exception is thrown during an asynchronous block, it is not actually thrown immediately. Instead, it will be thrown at the time you call await on the Deferred object that is returned.
To ignore any exceptions, launch the parent coroutine with the async function; however, if required to handle, the exception uses a try-catch block on the await() call on the Deferred object returned from async coroutine builder.
When using launch builder the exception will be stored in a Job object. To retrieve it, you can use the invokeOnCompletion helper function.
Add a CoroutineExceptionHandler to the parent coroutine context to catch unhandled exceptions and handle them.
CoroutineExceptionHandler is invoked only on exceptions that are not expected to be handled by the user; registering it in an async coroutine builder or the like has no effect.
When multiple children of a coroutine throw an exception, the general rule is the first exception wins.
Coroutines provide a way to wrap callbacks to hide the complexity of the asynchronous code handling away from the caller via a suspendCoroutine suspending function, which is included in the coroutine library.
Where to go from here?
Exception handling is a crucial step in working with asynchronous programming. If the basics are not clear, it makes the process of programming and dealing with various asynchronous tasks pretty complex. Thankfully, when it comes to coroutines, you are now well versed with the concepts and implementations.
Juwq ut, fio bafz itnfike zocpohcelw weqougekuw, te ob nu xo obma yo cpar xcec ylat otusujufv gjaj foyuafij.
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.