9.3 等待一个任务执行完毕
我们先来看一段代码:
1. fun firstCoroutineDemo() {
2. launch(CommonPool) {
3. delay(3000L, TimeUnit.MILLISECONDS)
4. println("[firstCoroutineDemo] Hello, 1")
5. }
7. launch(CommonPool, CoroutineStart.DEFAULT, {
8. delay(3000L, TimeUnit.MILLISECONDS)
9. println("[firstCoroutineDemo] Hello, 2")
10. })
11. println("[firstCoroutineDemo] World!")
12. }
运行这段代码,我们会发现只输出:
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. }
10. val c2 = launch(CommonPool) {
11. println("C2 Thread: ${Thread.currentThread()}")
12. println("C2 Start")
13. delay(5000L)
14. println("C2 World! 2")
15. }
17. println("Main Thread: ${Thread.currentThread()}")
18. println("Hello,")
19. println("Hi,")
20. println("c1 is active: ${c1.isActive} ${c1.isCompleted}")
21. println("c2 is active: ${c2.isActive} ${c2.isCompleted}")
22. }
再次运行:
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. }
10. val c2 = launch(CommonPool) {
11. println("C2 Thread: ${Thread.currentThread()}")
12. println("C2 Start")
13. delay(5000L)
14. println("C2 World! 2")
15. }
17. println("Main Thread: ${Thread.currentThread()}")
18. println("Hello,")
20. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
21. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
23. c1.join() // the main thread will wait until child coroutine completes
24. println("Hi,")
25. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
26. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
27. c2.join() // the main thread will wait until child coroutine completes
28. println("c1 is active: ${c1.isActive} isCompleted: ${c1.isCompleted}")
29. println("c2 is active: ${c2.isActive} isCompleted: ${c2.isCompleted}")
30. }
将会输出:
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. }
7. val c2 = launch(CommonPool) {
8. fc2()
9. }
10. ...
11. }
13. private suspend fun fc2() {
14. println("C2 Thread: ${Thread.currentThread()}")
15. println("C2 Start")
16. delay(5000L)
17. println("C2 World! 2")
18. }
20. private suspend fun fc1() {
21. println("C1 Thread: ${Thread.currentThread()}")
22. println("C1 Start")
23. delay(3000L)
24. println("C1 World! 1")
25. }
可以看出,我们这里的fc1, fc2函数是suspend fun。
