kotlin async exception handling

12,462

Solution 1

You can find your answer here: https://proandroiddev.com/kotlin-coroutines-patterns-anti-patterns-f9d12984c68e

To summarise, there are a few ways to catch exceptions with async.

1 - Wrap async call with supervisorScope

launch {
    supervisorScope {
        val task = async {
            methodThatThrowsException()
        }
        try {
            updateUI("Ok ${task.await()}")
        } catch (e: Throwable) {
            showError("Erro! ${e.message}")
        }
    }
}

2 - Passing a SupervisorJob as param

launch { 
    // parentJob (optional) is the parent Job of the CoroutineContext
    val task = async(SupervisorJob(parentJob)) {
        methodThatThrowsException()
    }
    try {
        updateUI("Ok ${task.await()}")
    } catch (e: Throwable) {
        showError("Erro! ${e.message}")
    }
}

3 - Wrapping async with coroutineScope

launch {
    try {
        coroutineScope {
            val task = async {
                methodThatThrowsException()
            }
            updateUI("Ok ${task.await()}")
        }
    } catch (e: Throwable) {
        showError("Erro! ${e.message}")
    }
}

Solution 2

In your second example, if you add Thread.sleep(1000) after your println("main end") statement you will see an exception as well. Without the sleep the application ends before the exception is thrown:

Exception in thread "DefaultDispatcher-worker-3" java.lang.Exception: aaa
at de.e2.app$Companion$main$job$1$1.invokeSuspend(AsyncProblem2.kt:26)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

In both cases you run into a standard behavior of structured concurrency introduced with Kotlin 1.3 (see https://medium.com/@elizarov/structured-concurrency-722d765aa952).

If in an async block an exception is thrown, the own coroutine is cancelled and the parent coroutines as well: see Kotlin coroutine can't handle exception

Share:
12,462
IulianT
Author by

IulianT

Updated on June 14, 2022

Comments

  • IulianT
    IulianT almost 2 years

    Given the following snippet, i do not understand why my android app crashes. I tested in a standalone kotlin app but this does not happen.

    class LoginActivity : AppCompatActivity(), CoroutineScope
    {
         lateinit var job: Job
        override val coroutineContext: CoroutineContext
            get() = Dispatchers.Main + job
    
    
       override fun onCreate(savedInstanceState: Bundle?)
        {
            super.onCreate(savedInstanceState)
            job = Job()
    
            try
            {
                launch()
                {
                    try
                    {
                        var res = async { test() }
    
                        res.await()
    
                    } 
                    catch (e2: java.lang.Exception)
                    {
    
                    }
                }
    
            }
            catch (e: java.lang.Exception)
            {
    
    
            }
        }
    
        fun test(): String
        {
            throw java.lang.Exception("test ex")
            return "";
        }
    }
    
    
     --------- beginning of crash
    E/AndroidRuntime: FATAL EXCEPTION: main
        Process: ro.ingr.ingeeasafety, PID: 11298
        java.lang.Exception: test ex
            at ro.ingr.ingeeasafety.activities.LoginActivity.test(LoginActivity.kt:72)
            at ro.ingr.ingeeasafety.activities.LoginActivity$onCreate$1$res$1.invokeSuspend(LoginActivity.kt:48)
            at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
            at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
            at android.os.Handler.handleCallback(Handler.java:751)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:154)
            at android.app.ActivityThread.main(ActivityThread.java:6119)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    

    Standalone kotlin app code, execution reaches "main end" println

    class app
    {
        companion object :CoroutineScope
        {
            lateinit var job: Job
            override val coroutineContext: CoroutineContext
                get() = Dispatchers.Default+ job
    
            init
            {
                job=Job()
            }
    
            @JvmStatic
            fun main(args: Array<String>)
            {
                launch()
                {
                    try
                    {
                        async()
                        {
                            println("async start")
                            throw Exception("aaa")
    
                        }.await()
                    }
                    catch (e: Exception)
                    {
                        println("async exception")
                    }
                }
    
    
                println("main end")
    
            }
        }
    }
    

    I am trying to create a flow where i load something from somewhere and if the load operation fails my app does not crash. I was expecting that the exception got caught in the handlers defined.

    LE: I added the crash stack trace.