跳至主要內容

深入分析协程

guodongAndroid大约 3 分钟

分析协程

目前为止,您已经了解到:每次启动协程时,您都将获得 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 标准库中的 delayrepeat 函数,您设法构建了一种机制,该机制在满足时间或逻辑条件之前尝试多次运行某些代码。您可以使用相同的机制来构建网络回退和重试逻辑。当您在本书后续章节学习了如何从协程中返回值,您会看到它的强大之处。


  1. 这里指《启动协程》中的代码 ↩︎