hoshiKuzu
hoshiKuzu

Reputation: 915

Kotlin async in coroutine: exception caught and still propagated?

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)

Upvotes: 4

Views: 2380

Answers (3)

mobiledev Alex
mobiledev Alex

Reputation: 2318

SupervisorJob has launch as Top coroutine and both async/await are nested coroutines.

Nested async/await always propagated up via Job hierarchy even if you use try/catch.

launch as Top coroutine require CoroutineExceptionHandler.

Alternatively you can:

  1. wrap both async and try/catch in supervisorScope
  2. wrap both async and try/catch in coroutineScope inside try/catch. But in this case second async will be cancelled after exception is thrown

Upvotes: 1

ibrahim
ibrahim

Reputation: 91

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.

Upvotes: -1

Siri
Siri

Reputation: 951

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

Upvotes: 3

Related Questions