首发于公众号: DSGtalk1989
28.协程基础
-
准备工作
如果你使用的是
Android studio
,在build.gradle
文件中,添加协程依赖。
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
}
在intelliJ IDEA
中需要进入到module setting
的dependency
添加maven
依赖同上
-
最常用的协程启动
CoroutineScope.launch
,我们可以看一下这个方法,有三个参数。public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
三个参数分别代表着,协程的上下文,启动模式和挂起的函数。
协程的上下文和启动模式都有默认值,不填写直接走默认。并且该方法最终会返回一个
Job
对象,我们点进去看一下。方法和参数不多,我们逐个的去看。
-
cancel
取消一个协程fun main() { val job = GlobalScope.launch { delay(1000L) println("World!") } job.cancel() println("Hello,") }
只有
Hello,
,没有World!
。因为协程被取消了。 -
join
等待协程执行完毕fun main() = runBlocking { val job = GlobalScope.launch { delay(1000L) println("World!") delay(1000L) } println("Hello,") job.join() println("Good!") }
和java中的
Thread.join
比较像,都是会阻塞当前线程,所以最终打印出来的是Hello, World! Good!
这里需要注意,
join
方法用suspend
修饰,是个挂起函数,挂起函数必须只能在协程中进行使用,所以外面包裹了一个runBlocking
,具体runBlocking
的含义我们之后分析。 -
cancelAndJoin
取消一个任务并且等到他彻底结束public suspend fun Job.cancelAndJoin() { cancel() return join() }
OK,我们回过来继续看一下协程的概念,本身的协程是非常的轻量级的线程。一开始我们就可以把它理解成一个与当前线程区别开来的线程。
-
-
runBlocking
很明显,
GlobalScope
的生命周期是全局的,即,完全起了一个线程,什么时候走完完全取决于整个应用程序的生命周期。我们来看这样一个效果fun main() { println("ready") GlobalScope.launch { println("GlobalScope.launch go!") delay(5000L) println("GlobalScope.launch end!") } println("end") }
由于主线程很快的就结束掉,我们完全没有给到协程启动的机会,jvm迅速的完成了主线程的任务。导致我们看到的控制台输出是这样的
ready end
实际上这里面我们希望的是能够让
main
方法把协程里面的内容跑完再结束的,这个时候就轮到我们的runBlocking
登场。Runs new coroutine and blocks current thread interruptibly until its completion.
即起一个新的协程,并且立马阻塞当前的线程,直到协程完毕。
这里我们需要来认识一下,协程中的
sleep
函数delay
,该函数只能用在挂起函数中,会让协程暂停一段时间。那么这样一来,我们结合起来看
fun main() { println("ready") // 当协程在后台等待时, 主线程继续执行 runBlocking { // 但是这个表达式阻塞了主线程 println("runBlocking start") delay(2000L) // ……我们延迟 2 秒来保证 JVM 的存活 println("runBlocking end") } println("end") }
能够很清晰的看到,主线程被阻塞了,因为
runBlocking
的概念,这样一来就可以达到delay
阻塞了主线程的效果。 -
协程中叠协程
协程中我们可以再起协程,举个例子,我们瞎看如下代码。
fun main() { println("ready") // 当协程在后台等待时, 主线程继续执行 runBlocking { // 但是这个表达式阻塞了主线程 GlobalScope.launch { // 在后台启动新的协程, 然后继续执行当前程序 println("GlobalScope.launch go!") delay(5000L) // 非阻塞, 等待 1 秒 (默认的时间单位是毫秒) println("GlobalScope.launch end!") } println("runBlocking start") delay(1000L) // ……我们延迟 2 秒来保证 JVM 的存活 } println("end") }
经过上文的分析,我们知道
runBlocking
会阻塞当前的线程,直到运行完它自己的协程,结果现在我们在协程中又通过GlobalScope.launch
起了一个全局的协程。考虑到前面讲到的生命周期问题,我们猜测runBlocking
不会去等GlobalScope.launch
,事实也确实如此。ready runBlocking start GlobalScope.launch go! end
我们现在去掉
GlobalScope
,直接走launch
。发现明显不同的生命周期,直接launch
的话,这个协程的生命周期就是runBlocking
,也就是说,哪怕launch
里面消耗的时间再长,main
函数也需要等到整个的runBlocking
生命周期走完即launch
里面的协程也结束才行。 -
协程到底如何轻量
repeat(10_000) { // 启动大量的协程 runBlocking { launch { print(".") } } }
启动一万个协程,轻飘飘的完成了打点的任务。
Kotlin学习笔记之 13 基础操作符run、with、let、also、apply