将异常从后台线程传播到主线程使用Kotlin协程

huangapple go评论51阅读模式
英文:

Propagate exception from background thread to main thread with Kotlin Coroutines

问题

With Kotlin Coroutines,我需要在同一个try块中同时并行运行两个I/O工作,而不会相互阻塞。如果其中任何一个工作失败,它应该在单个catch块中捕获。这两个操作是:

a)连续每10秒ping服务器。
b)使用okhttp进行网络调用。

我需要将这两个操作封装在一个suspend函数中。

以下是代码示例:

suspend fun sendAllRequests() {
    try {
        val pingJob = GlobalScope.launch { pingServer() }
        val networkCallJob = GlobalScope.launch { makeNetworkCall() }

        // 等待两个任务完成
        pingJob.join()
        networkCallJob.join()
    } catch (exception: CustomException) {
        // 处理异常
        throw RuntimeException("Custom Error")
    }
}

suspend fun pingServer() {
    while (true) {
        delay(10000)
        if (isPingSuccess) {
            Log.d("Connection active")
        } else {
            Log.d("Connection Inactive")
            throw RuntimeException("Ping failed")
        }
    }
}

suspend fun makeNetworkCall() {
    // 这是一个伪代码,仅用于说明
    try {
        okhttp.newCall().await()
    } catch (e: Exception) {
        // 处理网络调用异常
        throw CustomException("Network call failed", e)
    }
}

通过上述代码,你可以并行运行这两个长时间运行的任务,同时捕获它们抛出的异常,并在catch块中处理它们。不会相互阻塞。

英文:

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

得分: 1

根据这篇文章,实现你想要的方法的一个简单方式是不使用 launch,而是使用 async。然后,你可以运行你的 makeNetworkCall(),然后在你的异步延迟对象上使用 await 来捕获异常。

类似这样的代码:

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")    
    }
}

请注意以下事项:

  1. 启动或异步另一个协程并不意味着代码在后台线程上运行,你需要将你的协程启动到另一个调度程序上,例如 Dispatchers.IO。
  2. 避免使用 GlobalScope,这样做可能会导致内存泄漏。创建自己的作用域,或者如果你在Android上,可以尝试使用应用程序组件作用域,如 viewModelScope 或 lifecycleScope。
英文:

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

huangapple
  • 本文由 发表于 2023年5月17日 23:51:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76273974.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定