Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

kotlin async exception handling

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.

like image 206
IulianT Avatar asked Nov 14 '18 15:11

IulianT


2 Answers

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}")
    }
}
like image 188
nglauber Avatar answered Nov 11 '22 18:11

nglauber


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

like image 25
Rene Avatar answered Nov 11 '22 18:11

Rene