Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I restart/retry Kotlin coroutine after exception in Android

I am really happy that I switched my long running tasks, which constantly produce results to UI thread to coroutines. It improved performance and decreased memory usage by 3 times and all memory leaks disappeared compared to AsyncTask or regular Threads in Android.

The only problem remains is that, I don't know how should I restart my long running operation after exception has occurred at some time...

I feel I did not understand exception handling in coroutines at all after reading tons of article. Let me know how can I achieve desired behaviour.

  1. I have coroutine scope in fragment(will move to VM in near future).

    lateinit var initEngineJob: Job
    override val coroutineContext: CoroutineContext
        get() = initEngineJob + Dispatchers.Main

  1. Long running task with async/await.

fun initWorkEngineCoroutine()
    {
        launch {

            while(true) {
                val deferred = async(Dispatchers.Default) {
                    getResultsFromEngine()
                }
                    val result = deferred.await()
                    if (result != null) {
                    //UI thread
                        draw!!.showResult(result)
                    }
                }
            }

        }

fun getResultsFromEngine() :Result? {
           result = // some results from native c++ engine, which throws exception at some times
           return result
   }

i don't know where should I put try catch. I tried to surround deferred.await() with try catch, but I could not call same method in catch block to retry long running task. I tried SupervisorJob(), but no success either. I still could not call initWorkEngineCoroutine() again and start new coroutine...

Help to solve this issue finally :)

like image 368
Viktor Vostrikov Avatar asked Oct 15 '25 18:10

Viktor Vostrikov


1 Answers

You should treat your code as linear imperative and try/catch where it makes the most logical sense in your code. With this mindset, your question is probably less about coroutines and more about try/catch retry. You might do something like so:

fun main() {
    GlobalScope.launch {
        initWorkEngineCoroutine()
    }
}

suspend fun initWorkEngineCoroutine() {
    var failures = 0
    val maxFailures = 3
    while(failures <= maxFailures) {
        try {
             getResultsFromEngine()?.let {
                draw!!.showResult(it)
            }
        } catch (e: Exception) {
            failures++
        }
    }
}

// withContext is like async{}.await() except an exception occuring inside 
// withContext can be caught from inside the coroutine.
// here, we are mapping getResultFromEngine() to a call to withContext and 
// passing withContext the lambda which does the work
suspend fun getResultsFromEngine() :Result? = withContext(Dispatchers.Default) {
    Result()
}

I've included some logic to prevent infinite loop. It's probably not going to fit your requirements, but you might consider some sort of thing to prevent an issue where exceptions are raised immediately by getResultsFromEngine() and end up causing an infinite loop that could result in unexpected behavior and potential stackoverflow.

like image 179
methodsignature Avatar answered Oct 17 '25 08:10

methodsignature