避免在子协程异常时取消父作业
Avoid cancelling of parent job on exception on child coroutine
我正在 Android 上试验 在 Kotlin 协程中处理异常。
我的用例是我想在后台执行大量任务(以异步方式)并在单个 activity.
上更新多个 UI 组件
我设计了一个 BaseActivity
结构来实现 CoroutineScope
,因此我可以将调用的协程与 activity 的生命周期结合起来。
另外,我有一个 Repository
class 来处理网络调用。
我已经 运行 同时完成多项任务。我知道如果我使用单个 Job
对象取消 activity 的 onDestroy()
上的所有协同程序并在 activity 中执行 (launch
) 多个协同程序,异常任何单个协同程序都会从其 CoroutineContext
中取消 Job
。由于 Job
附加到 activity 的生命周期,它也会取消所有其他协程。
我试过使用 CoroutineExceptionHandler
。它捕获异常但也取消 Job
。结果取消了所有其他协程。
我想要什么?
- 能够使用单个
Job
对象附加activity 生命周期
- 一个协程中的异常不应该取消其他协程
在下面添加代码
class BaseActivity : AppCompatActivity(), CoroutineScope {
val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch(coroutineContext) {
Log.i("GURU", "launch1 -> start")
val result1Deferred = async { Repository().getData(1) }
val result2Deferred = async { Repository().getData(2) }
Log.i("GURU", "awaited result1 = " + result1Deferred.await() + result2Deferred.await())
}
//If Exception is Thrown, Launch1 should still continue to complete
advancedLaunch(coroutineContext) {
Log.i("GURU", "launch2 -> start")
val result1Deferred = async { Repository().getData(3) }
val result2Deferred = async { Repository().getData(4) }
delay(200)
throw Exception("Exception from launch 2")
Log.i("GURU", "awaited result2 = " + result1Deferred.await() + result2Deferred.await())
}
}
fun CoroutineScope.advancedLaunch(context: CoroutineContext = EmptyCoroutineContext,
exceptionBlock: (Throwable) -> Unit = {Log.i("GURU", it.message)},
launchBlock: suspend CoroutineScope.() -> Unit) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> exceptionBlock(throwable)}
launch(context + exceptionHandler) { launchBlock() }
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
Log.i("GURU", "job -> cancelled")
}
}
这个的日志结果是
I/GURU: launch1 -> start
I/GURU: launch2 -> start
I/GURU: getData -> start 1
I/GURU: getData -> start 2
I/GURU: getData -> start 4
I/GURU: getData -> start 3
I/GURU: Exception from launch 2
--------- beginning of crash
您可能希望将 Job
替换为 SupervisorJob
。
它防止异常传播"upwards"(一个失败child不会导致整个作业失败),但仍然允许您推送取消"downwards"(到运行 child仁).
我正在 Android 上试验 在 Kotlin 协程中处理异常。
我的用例是我想在后台执行大量任务(以异步方式)并在单个 activity.
上更新多个 UI 组件我设计了一个 BaseActivity
结构来实现 CoroutineScope
,因此我可以将调用的协程与 activity 的生命周期结合起来。
另外,我有一个 Repository
class 来处理网络调用。
我已经 运行 同时完成多项任务。我知道如果我使用单个 Job
对象取消 activity 的 onDestroy()
上的所有协同程序并在 activity 中执行 (launch
) 多个协同程序,异常任何单个协同程序都会从其 CoroutineContext
中取消 Job
。由于 Job
附加到 activity 的生命周期,它也会取消所有其他协程。
我试过使用 CoroutineExceptionHandler
。它捕获异常但也取消 Job
。结果取消了所有其他协程。
我想要什么?
- 能够使用单个
Job
对象附加activity 生命周期 - 一个协程中的异常不应该取消其他协程
在下面添加代码
class BaseActivity : AppCompatActivity(), CoroutineScope {
val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch(coroutineContext) {
Log.i("GURU", "launch1 -> start")
val result1Deferred = async { Repository().getData(1) }
val result2Deferred = async { Repository().getData(2) }
Log.i("GURU", "awaited result1 = " + result1Deferred.await() + result2Deferred.await())
}
//If Exception is Thrown, Launch1 should still continue to complete
advancedLaunch(coroutineContext) {
Log.i("GURU", "launch2 -> start")
val result1Deferred = async { Repository().getData(3) }
val result2Deferred = async { Repository().getData(4) }
delay(200)
throw Exception("Exception from launch 2")
Log.i("GURU", "awaited result2 = " + result1Deferred.await() + result2Deferred.await())
}
}
fun CoroutineScope.advancedLaunch(context: CoroutineContext = EmptyCoroutineContext,
exceptionBlock: (Throwable) -> Unit = {Log.i("GURU", it.message)},
launchBlock: suspend CoroutineScope.() -> Unit) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> exceptionBlock(throwable)}
launch(context + exceptionHandler) { launchBlock() }
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
Log.i("GURU", "job -> cancelled")
}
}
这个的日志结果是
I/GURU: launch1 -> start
I/GURU: launch2 -> start
I/GURU: getData -> start 1
I/GURU: getData -> start 2
I/GURU: getData -> start 4
I/GURU: getData -> start 3
I/GURU: Exception from launch 2
--------- beginning of crash
您可能希望将 Job
替换为 SupervisorJob
。
它防止异常传播"upwards"(一个失败child不会导致整个作业失败),但仍然允许您推送取消"downwards"(到运行 child仁).