9.3 等待一个任务执行完毕

我们先来看一段代码:

  1. fun firstCoroutineDemo() {
  2. launch(CommonPool) {
  3. delay(3000L, TimeUnit.MILLISECONDS)
  4. println("[firstCoroutineDemo] Hello, 1")
  5. }
  6. launch(CommonPool, CoroutineStart.DEFAULT, {
  7. delay(3000L, TimeUnit.MILLISECONDS)
  8. println("[firstCoroutineDemo] Hello, 2")
  9. })
  10. println("[firstCoroutineDemo] World!")
  11. }

运行这段代码,我们会发现只输出:

  1. [firstCoroutineDemo] World!

这是为什么?

为了弄清上面的代码执行的内部过程,我们打印一些日志看下:

  1. fun testJoinCoroutine() = runBlocking<Unit> {
  2. // Start a coroutine
  3. val c1 = launch(CommonPool) {
  4. println("C1 Thread: ${Thread.currentThread()}")
  5. println("C1 Start")
  6. delay(3000L)
  7. println("C1 World! 1")
  8. }
  9. val c2 = launch(CommonPool) {
  10. println("C2 Thread: ${Thread.currentThread()}")
  11. println("C2 Start")
  12. delay(5000L)
  13. println("C2 World! 2")
  14. }
  15. println("Main Thread: ${Thread.currentThread()}")
  16. println("Hello,")
  17. println("Hi,")
  18. println("c1 is active: ${c1.isActive} ${c1.isCompleted}")
  19. println("c2 is active: ${c2.isActive} ${c2.isCompleted}")
  20. }

再次运行:

  1. C1 Thread: Thread[ForkJoinPool.commonPool-worker-1,5,main]
  2. C1 Start
  3. C2 Thread: Thread[ForkJoinPool.commonPool-worker-2,5,main]
  4. C2 Start
  5. Main Thread: Thread[main,5,main]
  6. Hello,
  7. Hi,
  8. c1 is active: true false
  9. 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, 让主线程一直等到当前协程执行完毕再结束, 例如下面的这段代码

  1. fun testJoinCoroutine() = runBlocking<Unit> {
  2. // Start a coroutine
  3. val c1 = launch(CommonPool) {
  4. println("C1 Thread: ${Thread.currentThread()}")
  5. println("C1 Start")
  6. delay(3000L)
  7. println("C1 World! 1")
  8. }
  9. val c2 = launch(CommonPool) {
  10. println("C2 Thread: ${Thread.currentThread()}")
  11. println("C2 Start")
  12. delay(5000L)
  13. println("C2 World! 2")
  14. }
  15. println("Main Thread: ${Thread.currentThread()}")
  16. println("Hello,")
  17. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
  18. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
  19. c1.join() // the main thread will wait until child coroutine completes
  20. println("Hi,")
  21. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
  22. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
  23. c2.join() // the main thread will wait until child coroutine completes
  24. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
  25. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
  26. }

将会输出:

  1. C1 Thread: Thread[ForkJoinPool.commonPool-worker-1,5,main]
  2. C1 Start
  3. C2 Thread: Thread[ForkJoinPool.commonPool-worker-2,5,main]
  4. C2 Start
  5. Main Thread: Thread[main,5,main]
  6. Hello,
  7. c1 is active: true isCompleted: false
  8. c2 is active: true isCompleted: false
  9. C1 World! 1
  10. Hi,
  11. c1 is active: false isCompleted: true
  12. c2 is active: true isCompleted: false
  13. C2 World! 2
  14. c1 is active: false isCompleted: true
  15. c2 is active: false isCompleted: true

通常,良好的代码风格我们会把一个单独的逻辑放到一个独立的函数中,我们可以重构上面的代码如下:

  1. fun testJoinCoroutine2() = runBlocking<Unit> {
  2. // Start a coroutine
  3. val c1 = launch(CommonPool) {
  4. fc1()
  5. }
  6. val c2 = launch(CommonPool) {
  7. fc2()
  8. }
  9. ...
  10. }
  11. private suspend fun fc2() {
  12. println("C2 Thread: ${Thread.currentThread()}")
  13. println("C2 Start")
  14. delay(5000L)
  15. println("C2 World! 2")
  16. }
  17. private suspend fun fc1() {
  18. println("C1 Thread: ${Thread.currentThread()}")
  19. println("C1 Start")
  20. delay(3000L)
  21. println("C1 World! 1")
  22. }

可以看出,我们这里的fc1, fc2函数是suspend fun。