我们先来看一段代码:
fun firstCoroutineDemo() {
launch(CommonPool) {
delay(3000L, TimeUnit.MILLISECONDS)
println("[firstCoroutineDemo] Hello, 1")
}
launch(CommonPool, CoroutineStart.DEFAULT, {
delay(3000L, TimeUnit.MILLISECONDS)
println("[firstCoroutineDemo] Hello, 2")
})
println("[firstCoroutineDemo] World!")
}
运行这段代码,我们会发现只输出:
[firstCoroutineDemo] World!
这是为什么?
为了弄清上面的代码执行的内部过程,我们打印一些日志看下:
fun testJoinCoroutine() = runBlocking<Unit> {
// Start a coroutine
val c1 = launch(CommonPool) {
println("C1 Thread: ${Thread.currentThread()}")
println("C1 Start")
delay(3000L)
println("C1 World! 1")
}
val c2 = launch(CommonPool) {
println("C2 Thread: ${Thread.currentThread()}")
println("C2 Start")
delay(5000L)
println("C2 World! 2")
}
println("Main Thread: ${Thread.currentThread()}")
println("Hello,")
println("Hi,")
println("c1 is active: ${c1.isActive} ${c1.isCompleted}")
println("c2 is active: ${c2.isActive} ${c2.isCompleted}")
}
再次运行:
C1 Thread: Thread[ForkJoinPool.commonPool-worker-1,5,main]
C1 Start
C2 Thread: Thread[ForkJoinPool.commonPool-worker-2,5,main]
C2 Start
Main Thread: Thread[main,5,main]
Hello,
Hi,
c1 is active: true false
c2 is active: true false
我们可以看到,这里的C1、C2代码也开始执行了,使用的是ForkJoinPool.commonPool-worker线程池中的worker线程。但是,我们在代码执行到最后打印出这两个协程的状态isCompleted都是false,这表明我们的C1、C2的代码,在Main Thread结束的时刻(此时的运行main函数的Java进程也退出了),还没有执行完毕,然后就跟着主线程一起退出结束了。
所以我们可以得出结论:运行 main () 函数的主线程, 必须要等到我们的协程完成之前结束 , 否则我们的程序在 打印Hello, 1和Hello, 2之前就直接结束掉了。
我们怎样让这两个协程参与到主线程的时间顺序里呢?我们可以使用join, 让主线程一直等到当前协程执行完毕再结束, 例如下面的这段代码
fun testJoinCoroutine() = runBlocking<Unit> {
// Start a coroutine
val c1 = launch(CommonPool) {
println("C1 Thread: ${Thread.currentThread()}")
println("C1 Start")
delay(3000L)
println("C1 World! 1")
}
val c2 = launch(CommonPool) {
println("C2 Thread: ${Thread.currentThread()}")
println("C2 Start")
delay(5000L)
println("C2 World! 2")
}
println("Main Thread: ${Thread.currentThread()}")
println("Hello,")
println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
c1.join() // the main thread will wait until child coroutine completes
println("Hi,")
println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
c2.join() // the main thread will wait until child coroutine completes
println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
}
将会输出:
C1 Thread: Thread[ForkJoinPool.commonPool-worker-1,5,main]
C1 Start
C2 Thread: Thread[ForkJoinPool.commonPool-worker-2,5,main]
C2 Start
Main Thread: Thread[main,5,main]
Hello,
c1 is active: true isCompleted: false
c2 is active: true isCompleted: false
C1 World! 1
Hi,
c1 is active: false isCompleted: true
c2 is active: true isCompleted: false
C2 World! 2
c1 is active: false isCompleted: true
c2 is active: false isCompleted: true
通常,良好的代码风格我们会把一个单独的逻辑放到一个独立的函数中,我们可以重构上面的代码如下:
fun testJoinCoroutine2() = runBlocking<Unit> {
// Start a coroutine
val c1 = launch(CommonPool) {
fc1()
}
val c2 = launch(CommonPool) {
fc2()
}
...
}
private suspend fun fc2() {
println("C2 Thread: ${Thread.currentThread()}")
println("C2 Start")
delay(5000L)
println("C2 World! 2")
}
private suspend fun fc1() {
println("C1 Thread: ${Thread.currentThread()}")
println("C1 Start")
delay(3000L)
println("C1 World! 1")
}
可以看出,我们这里的fc1, fc2函数是suspend fun。