-
在系列一中,我们已经对协程有了初步的了解,我们在此篇继续。前面我们是通过GlobalScope.launch{}这个协程建造器来创建协程的,它的特点就是:
Launches a new coroutine without blocking the current thread.
即不会阻塞当前的线程的运行。我们再来接触一个不一样的协程建造器: runBlocking{}
-
先来看看对于runBlocking{}的文档描述:
Runs a new coroutine and blocks the current thread _interruptibly_ until its completion.
核心就是一个单词,block,会阻塞当前线程,这点是和.launch{}是大不相同的,即只有runBlocking中的代码执行完毕后,当前线程才会被唤醒。我们接下来通过代码来感受一下。
-
直接来看代码:
fun main() { GlobalScope.launch { delay(1000) println("world") } println("hello") runBlocking { delay(2000) } println("welcome") }
同样按照之前的时序发展方式,我们来分析一下这段代码:
- 0s: 通过launch{}方式创建了一个协程并且开始delay;由于launch{}方式不会阻塞当前协程,所以会紧接着打印“hello”; 通过runBlocking{}的方式创建了一个协程并开始delay;由于runBlocking{}会阻塞线程,所以之后的代码此时不会得到执行
- 1s: launch{}协程被唤醒,打印“world”
- 2s: runBlocking{}协程被唤醒,然后该协程直接结束;当前线程被阻塞完毕,继续执行,打印“welcome”
所以最终的输出结果就是:
hello (暂停1s) world (暂停1s) welcome
-
通过上面的阐述,相信大家对于这两种方式创建出来的协程的不同特点。那么如果我们把这两者进行嵌套使用呢?
fun main() = runBlocking { GlobalScope.launch { delay(1000) println("world") } println("hello") delay(2000) println("welcome") }
输出的结果和之前的例子一样。但是将delay(2000)换成delay(500)结果就不一样了,大家可以去试试,原因的话我们在下一篇去解释。
-
我们再来注意一下GlobalScope.launch{}:
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { .... }
会发现它实际上会返回一个Job类型的引用,何为Job?
A background job. Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion.
job就是一个待执行的任务,并且能够被取消,有对应的生命周期:_New_、_Active_、_Completing_、_Cancelling_、_Cancelled_、_Completed_.如果非要类比的话,感觉有点像Runnable,并且也是没有返回值的。类似的还有个Deferred,这个是有返回值的,那么这个就有点像Future,后面有专门的篇章再去介绍。今天我们只需要关注它的一个方法:join()
1.Suspends the coroutine until this job is complete. 2.Note that the job becomes complete only when all its children are complete.
这是我从文档上摘取出来的两句:1. 会挂起协程(job的父协程),直到该job执行完毕;2. 只有当某个协程的所有子协程都执行完毕后,才意味着该协程执行完毕。有点抽象,我们来看段代码:
fun main() = runBlocking { val job = GlobalScope.launch { delay(1000) println("world") } println("hello") job.join() println("welcome") }
一运行:
hello (等待1s) world welcome
这里先通过runBlocking创建了协程A,在协程A作用域里又通过launch创建了协程B,这样协程B就成了协程A的子协程。这里的job是子协程B的,所以当执行到job.join()这一行时,会挂起父协程A。只有当job执行完毕后,父协程的执行流程才会恢复(resume),所以就能解释上面的输出结果了。读者可以按照我们前面的时序分析法来分析输出结果,我这里就不再赘述了。
-
那么job.join()的现实意义在哪?从某种意义上讲,实现了两个协程间的协作与同步。之前我们的所有代码都是通过delay的方式来达到这种顺序输出的效果,实际开发中我们并不知道这个job的完成需要多久,比如就是常见的网络请求,你根本不知道请求啥时候会返回(job的delay可以看作是这个操作的模拟),就不能在父协程中通过delay的方式来实现同步。但是毫无疑问,join是可以做到的。
Kotlin学习系列之:协程的创建(二)
最新推荐文章于 2024-07-20 10:51:28 发布