小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门
Job
val job = lifecycleScope.launch { }
Job 用于处理协程。对于每一个您所创建的协程 (通过 launch 或者 async),它会返回一个 Job 实例,该实例是协程的唯一标识,并且负责管理协程的生命周期。正如我们上面看到的,您可以将 Job 实例传递给CoroutineScope 来控制其生命周期。
一个Job中可以关联多个子Job,同时它也提供了通过外部传入parent的实现
public fun Job(parent: Job? = null): Job = JobImpl(parent)
这个很好理解,当传入parent时,此时的Job将会作为parent的子Job。
Job 的生命周期
既然Job是来管理协程的,那么它提供了六种状态来表示协程的运行状态。
New: 创建Active: 运行Completing: 已经完成等待自身的子协程Completed: 完成Cancelling: 正在进行取消或者失败Cancelled: 取消或失败
这六种状态Job对外暴露了三种状态,它们随时可以通过Job来获取
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
如果协程处于活跃状态,协程运行出错或者调用 job.cancel() 都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)。当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true。
val scope = CoroutineScope(Job())
scope.launch() {
// new coroutine -> can suspend
launch {
// Child 1
}
launch {
// Child 2
}
}
上面已经提及到一个Job可以有多个子Job,所以一个Job的完成都必须等待它内部所有的子Job完成;对应的cancel也是一样的。
默认情况下,如果内部的子Job发生异常,那么它对应的parent Job与它相关连的其它子Job都将取消运行。俗称连锁反应。
我们也可以改变这种默认机制,Kotlin提供了SupervisorJob来改变这种机制。这种情况还是很常见的,例如用协程请求两个接口,但并不想因为其中一个接口失败导致另外的接口也不请求,这时就可以使用SupervisorJob来改变协程的这种默认机制。
SupervisorJob
使用 SupervisorJob 时,一个子协程的运行失败不会影响到其他子协程。SupervisorJob 不会取消它和它自己的子级,也不会传播异常并传递给它的父级,它会让子协程自己处理异常。
所以SupervisorJob只有下面两种使用方式。
- supervisorScope{}
- CoroutineScope(SupervisorJob())
这里有个误区,那就是大家不要以为使用SupervisorJob之后,协程就不会崩溃,不管你用什么Job,该崩溃的还是要崩溃的,它们的差别在于是否会影响到别的协程
SupervisorJob使用
fun main() {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
// Child 1
println("Child 1")
throw error("Child 1 error ")
}
scope.launch {
// Child 2
println("Child 2 before")
delay(1000)
println("Child 2")
}
//这里的sleep只是保持进程存活, 目的是为了等待协程执行完
Thread.sleep(3000)
}
如果使用CoroutineScope(Job()),Child 1抛出异常后Child 2也将被取消(取消是有条件的,🔺 注意: kotlinx.coroutines包下的所有挂起函数都是可取消的),改用CoroutineScope(SupervisorJob())后Child 1抛出异常后Child 2还会继续执行.
supervisorScope{}使用
suspend fun main() {
supervisorScope {
launch {
// Child 1
println("Child 1")
throw error("Child 1 error ")
}
launch {
// Child 2
println("Child 2 before")
delay(1000)
println("Child 2")
}
}
//这里的sleep只是保持进程存活, 目的是为了等待协程执行完
Thread.sleep(3000)
}
这种写法同上面效果一样
❌ 注意:错误写法
方式一 ❌
val scope = CoroutineScope(Job())
scope.launch(SupervisorJob()) {
// new coroutine -> can suspend
launch {
// Child 1
}
launch {
// Child 2
}
}
方式二 ❌
val scope = CoroutineScope(SupervisorJob())
scope.launch() {
// new coroutine -> can suspend
launch {
// Child 1
}
launch {
// Child 2
}
}
上面两种写法并不能起到SupervisorJob的作用
因为新的协程被创建时,会生成新的 Job 实例