Propagate exception from background thread to main thread with Kotlin Coroutines

488 Views Asked by At

With Kotlin Coroutines I need to run two I/O work inside a try block at same time parallely without blocking each other.
And if any of the work fails, then it should be caught inside a single catch block.
Two operations are:
a) Continuously ping the server at a 10 second interval.
b) Make a network call with okhttp.

I need to cover these two operations inside a suspend function. PSB code:

suspend fun sendAllRequests() {
    try {
      pingServer()
    
      makeNetworkCall()
    
    } catch(exception: CustomException) {
      // Do some other work and throw exception to UI
      throw RuntimeException("Custom Error")    
    }
}
suspend fun pingServer() {
      job = GlobalScope.launch {
      while(true) {
         delay(10000) {
         if(isPingSuccess) {
           Log("Connection active")
         } else {
           Log("Connection Inactive")
           job.cancel()
           throw RuntimeException("Ping failed")
        }
      } 
    }
  }

suspend fun makeNetworkCall() {
    //This is a pseudo code just for explanation
    okhttp.newCall().await() 
    onFailure -> throw Exception
}

The challenge with above approach is that it, only initiates the logic present inside function pingServer().
i.e. The function makeNetworkCall() is never triggered.

And if I wrap the first function inside a new coroutine scope like below:
then both job works fine, but the exception thrown by pingServer() is never caught in catch block.

try {
   // With below logic, both functions are triggered 
   scope.launch { pingServer() } 

   makeNetworkCall()
} catch {
  // but pingServer() failure never reaches here
  // and makeNetworkCall() failure is properly captured here
}

How can I run the above two long running tasks parallely without blocking each other ?
How can I ensure that if any of them throws an exception, then it is caught inside the same catch block as shown above ?

1

There are 1 best solutions below

1
On

Based on this article, an easy way to achieve what you are after is to not use launch but use async. Then you can run your makeNetworkCall() and after that await on your async deferred object catching the exception there

Something like this:

suspend fun sendAllRequests() {
    try {
      val deferred = GlobalScope.async { pingServer() }
    
      makeNetworkCall()
      deferred.await()

    } catch(exception: CustomException) {
      // Do some other work and throw exception to UI
      throw RuntimeException("Custom Error")    
    }
}

Please also note:

  1. The fact that you launch or async on another coroutine does not mean that code runs on background thread, you will need to launch your coroutine to another dispatcher for this (eg. Dispatchers.IO)
  2. Avoid using GlobalScope, you can easily create memory leaks doing so. Create your own scope or if you are on Android, try using the App Components scopes like viewModelScope or lifecycleScope