首发于公众号: 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
