深入分析协程
分析协程
目前为止,您已经了解到:每次启动协程时,您都将获得 Job 引用。您也可以在两个不同的 Job 之间建立依赖关系 — 如何实现呢?您只需要将前面[1]的代码替换成下面这样即可:
fun main() {
val job1 = GlobalScope.launch(start = CoroutineStart.LAZY) {
delay(200)
println("Pong")
delay(200)
}
GlobalScope.launch {
delay(200)
println("Ping")
job1.join()
println("Ping")
delay(200)
}
Thread.sleep(1000)
}
并导入 CoroutineStart
。浏览上面的代码:
- 您启动的第一个协程包含一些延迟代码并且输出
Pong
字符,将创建的 Job 对象赋值给 job1 引用。 - 您启动的第二个协程同样包含一些
println
输出,同时也将调用 job1 的join
函数。
上面代码的预期输出是什么呢?如果您的预期是先输出 Pong
再输出两次 Ping
,但是,实际上并非如此。如您所见,在第一个协程中您传入了 CoroutineStart.LAZY
,这意味着其相关的代码将在您实际需要它时被执行。
它会在第二个协程调用 job1 的 join
函数时执行。这就是为什么上面代码实际上先输出 Ping
,然后输出 Pong
,最后再输出 Ping
。
构建 Job 层级
在前面的代码中,您在不同的 Job 实例之间建立了依赖关系,但是这并不是您认为的那种父子关系。 请再一次用下面的代码替换前面的代码。您可以使用 with 来避免重复使用 GlobalScope 接收器:
fun main() {
with(GlobalScope) {
val parentJob = launch {
delay(200)
println("I’m the parent")
delay(200)
}
launch(context = parentJob) {
delay(200)
println("I’m a child")
delay(200)
}
if (parentJob.children.iterator().hasNext()) {
println("The Job has children!")
} else {
println("The Job has NO children")
}
Thread.sleep(1000)
}
}
依次浏览上面的代码:
- 首先,您启动了一个协程,并把创建的 Job 对象赋值给了 parentJob 引用。
- 然后,您使用前面的 Job 作为协程的
CoroutineContext
启动了另一个协程。因为 Job 抽象实现了CoroutineContext
。此时,您传递的CoroutineContext
与当前活跃的CoroutineScope EmptyCoroutineContext
中的一个合并。
如果您运行上面的代码,您可以看到 parentJob 是有子节点的。如果您删除第二个协程构建器的上下文,再运行相同的代码,您可以看到父子关系没有建立并且子节点不存在。
使用协程标准函数
您可以使用协程做的另一件事是构建重试逻辑机制。标准库中的 repeat
函数,与您在前面学到的 delay
函数配合使用,您可以创建尝试在延迟时间段内运行工作的代码。接下来,再次将 Main.kt 代码替换为以下代码:
fun main() {
var isDoorOpen = false
println("Unlocking the door... please wait.\n")
GlobalScope.launch {
delay(3000)
isDoorOpen = true
}
GlobalScope.launch {
repeat(4) {
println("Trying to open the door...\n")
delay(800)
if (isDoorOpen) {
println("Opened the door!\n")
} else {
println("The door is still locked\n")
}
}
}
Thread.sleep(5000)
}
尝试运行上面代码。您可以看到在最终打开门之前,可以尝试多次去打开门。因此,使用 Kotlin 标准库中的 delay
和 repeat
函数,您设法构建了一种机制,该机制在满足时间或逻辑条件之前尝试多次运行某些代码。您可以使用相同的机制来构建网络回退和重试逻辑。当您在本书后续章节学习了如何从协程中返回值,您会看到它的强大之处。
这里指《启动协程》中的代码 ↩︎