Kotlin async in coroutine: exception caught and still propagated?

Viewed 454

I am running a coroutine in a SupervisorJob with a try/catch block surrounding only the await calls. The exception from the async block gets caught by the try/catch, but it still gets propagated and the app crashes.

This is what I have:

CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
            val a = async {
                delay(500)
                throw Exception("excep a")
                2
            }
            val b = async {
                delay(500)
                3
            }
            try {
                println(a.await() + b.await())
            } catch (e: Exception) {
                println("exception: ${e.message}")
            }
        }

This is what I get (note that "excep a" gets caught):

exception: excep a
Exception in thread "DefaultDispatcher-worker-3 @coroutine#1" java.lang.Exception: excep a
    at com.example.app.AuthTest$co2$1$a$1.invokeSuspend(AuthTest.kt:314)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
2 Answers

The exception handling mechanism of supervisorJob and Job is not the same. For supervisory tasks, the transmission of exceptions can only be from the parent scope to the child scope, and the propagation direction of the exception is one-way. Therefore, it is necessary to handle exceptions by itself for monitoring the scope of opening the coroutine.

    CoroutineScope(Dispatchers.IO + SupervisorJob()).launch(CoroutineExceptionHandler { _, throwable ->
        println("$throwable")
    }) {
        val a = async {
            delay(500)
            throw Exception("excep a")
            2
        }
        val b = async {
            delay(500)
            3
        }
        println(a.await() + b.await())
    }

This exception will be handled in the top-level Job

You are creating a parent coroutine using launch builder having IO dispatcher and SupervisorJob. Then you are creating two children coroutines using async biulder. But remember the child coroutines do not inherit the Job from its parent. Each child coroutine creates their own Job. So to avoid app crashing and exception apply the same supervisorjob explicitly to each child as shown below.

 val supervisorJob = SupervisorJob()
    CoroutineScope(Dispatchers.IO).launch {
        val a = async(supervisorJob) {
            delay(500)
            throw Exception("excep a")
            2
        }
        val b = async(supervisorJob) {
            delay(500)
            3
        }
        try {
            println(a.await() + b.await())
        } catch (e: Exception) {
            println("exception: ${e.message}")
        }
    }

Thank you.

Related