1. 区别
coroutineScope
和 supervisorScope
都是用来创建一个 CoroutineScope
并执行代码块,创建的 CoroutineScope
将继承上一级 CoroutineScope
的 CoroutineScope.coroutineContext
,但是会重写 croutineContext
的 Job
。
它们的区别在于 coroutineScope
的 coroutine
是一个 ScopeCoroutine
,而 supervisorScope
的 coroutine
是一个 SupervisorCoroutine
。SupervisorCoroutine
继承自 ScopeCoroutine
但是重写了其父类 JobSupport
的方法 childCancelled(cause: Throwable): Boolean
并返回 false
。这就是它们之间的唯一区别。
这一区别导致:
- 在
coroutineScope
中,只要任意一个子协程发生异常,整个scope
都会执行失败,并且其余的所有子协程都会被取消掉; - 在
supervisorScope
中,一个子协程的异常不会影响整个scope
的执行,也不会影响其余子协程的执行;
这两种 CoroutineScope
创建方式的区别只在于当发生异常时,它们对异常的处理方式各不相同。
我们接下来用例子详细说明它们各自如何处理异常。
2. 举例说明
若想统一处理协程的异常,防止异常引发程序崩溃,可以给协程设置 CoroutineExceptionHandler
,我们先定义一个协程异常处理器,如下,简单地把异常信息打印出来:
private val exceptionHandler = CoroutineExceptionHandler{ _, e ->
e.message?.let { Log.e("crx", "异常信息: $it") }
}
我们在 Activity
的 lifecycleScope
中来执行测试代码:
lifecycleScope.launch(exceptionHandler) {
//
... ...
//
Log.e("crx", "在执行完了Scope之后")
}
后面的测试代码,就是添加在上面省略号的位置。
2.1 launch发起的协程
我们分别定义两个方法,如下:
private suspend fun testSupervisorScope() = supervisorScope {
launch { throw IllegalArgumentException("随便抛一个异常") }
launch {
delay(1000)
Log.e("crx", "另一个协程")
}
}
private suspend fun testCoroutineScope() = coroutineScope {
launch { throw IllegalArgumentException("随便抛一个异常") }
launch {
delay(1000)
Log.e("crx", "另一个协程")
}
}
这两个方法唯一的区别在于一个是 supervisorScope
,一个是 coroutineScope
;
分别执行这两个测试方法,打印结果如下:
//执行testSupervisorScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常
E/crx: 另一个协程
E/crx: 在执行完了Scope之后
//执行testCoroutineScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常
可以得出以下结论:
由
launch
发起的协程,对于supervisorScope
,即使有一个子协发生了异常,其余子协程仍然可以正常执行,且整个scope
都可以正常执行;而对于coroutineScope
,一旦有某个子协程发生异常,其余子协程将被取消,且会导致整个scope
执行失败。
2.2 async发起的协程
测试代码和2.1完全一样,唯一不同是把 launch
换成了 async
,如下:
private suspend fun testSupervisorScope() = supervisorScope {
async { throw IllegalArgumentException("随便抛一个异常") }
async {
delay(1000)
Log.e("crx", "另一个协程")
}
}
private suspend fun testCoroutineScope() = coroutineScope {
async { throw IllegalArgumentException("随便抛一个异常") }
async {
delay(1000)
Log.e("crx", "另一个协程")
}
}
分别执行,打印结果如下:
//执行testSupervisorScope方法打印的结果
E/crx: 另一个协程
E/crx: 在执行完了Scope之后
//执行testCoroutineScope方法打印的结果
E/crx: 异常信息: 随便抛一个异常
可以看到,在 supervisorScope
中,async
并没有抛异常,另一个子协程顺利执行,整个 scope
也执行完毕;在 croutineScope
中,执行结果和 launch
的执行结果一样。
如果调用 async
的 await
方法,supervisorScope
则会抛异常,但仍然不会影响其余子协程的执行,相关示例这里就不展示了。
结论:
由
async
方法启动的协程,对于supervisorScope
,如果不调用async
的await
方法,协程就不会抛异常,如果调用,则会抛异常,但不管是否抛异常,其余子协程和整个scope
都可正常执行;对于coroutineScope
,只要某个子协程发生异常,就会影响整个scope
和其余子协程的执行。