Kotlin协程快速入门
协程,全称可以译作协同程序,很多语言都有这个概念和具体实现,之前入门Python的时候接触过,而Kotlin其实也早就有这个扩展功能库了,只不过之前一直处于实验阶段,不过前段时间1.0的正式版终于出了,网上的相关博客也多了起来,经过这几天的学习我也来做下小结吧。
环境配置
首先贴下Kotlin协程的官方github地址kotlinx.coroutines,下面的配置都是参照这里的说明,而且里面还贴心的给我们准备了很多基础的示例代码,感兴趣的的小伙伴稍后可以去看看。
首先配置下Kotlin版本
buildscript {
ext.kotlin_version = '1.3.11'
}
然后引入依赖,目前最新版是1.1.0
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0'
配置很简单,接下来干什么呢。当然是写个协程版的Hello World了!
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 创建并启动一个协程
delay(1000L) // 延迟(挂起)1000毫秒,注意这不会阻塞线程
println("World!") //延迟之后执行打印
}
println("Hello,") // 协程延迟的时候不会影响主线程的执行
Thread.sleep(2000L) // 阻塞线程2s,保证JVM存活,协程可正常执行完
}
运行结果:
2022-06-06 10:37:03.994 21112-21112/com.tq.myapplication I/System.out: Hello,
2022-06-06 10:37:04.997 21112-21302/com.tq.myapplication I/System.out: World!
基础语法
启动模式
上面的协程启动模式是默认的DEAFAULT,也就是创建并立即启动的,我们也可以设置启动模式为LAZY,来自己安排是什么时候需要启动:
fun main() {
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
println("World!")
}
println("Hello,")
job.start()
Thread.sleep(2000L)
}
CoroutineScope.launch一共有三个参数,然后介绍其他两个:
- context: CoroutineContext = EmptyCoroutineContext:协程上下文
- block: suspend CoroutineScope.() -> Unit:闭包参数,定义协程内需要执行的操作。
返回值为Job对象。
Job类
通过上面的例子,我们知道了一个重要的点launch函数是有返回值的,它是一个Job的接口类型,除了配合LAZY来自己启动一个协程,下面介绍下其他几个重要方法:
- job.cancel()取消一个协程
fun main() {
val job = GlobalScope.launch {
delay(1000L)
println("World!")
}
job.cancel()
println("Hello,")
}
协程被取消了,所以只打印了Hello,
- join()等待协程执行完毕
fun main() = runBlocking {
val job = GlobalScope.launch {
delay(1000L)
println("World!")
delay(1000L)
}
println("Hello,")
job.join()
println("Good!")
}
作用很像Thread.join()函数,join()后面的代码会等到协程结束再执行,结果如下:
2022-06-06 16:50:59.641 29843-29843/com.tq.myapplication I/System.out: Hello,
2022-06-06 16:51:00.647 29843-29881/com.tq.myapplication I/System.out: World!
2022-06-06 16:51:01.650 29843-29843/com.tq.myapplication I/System.out: Good!
- job.cancelAndJoin()等待协程执行完毕然后再取消 这是一个 Job 的扩展函数,它结合了 cancel 和 join的调用,来看下它的实现:
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}
挂起函数
细心的同学可能发现了两个不通点,Job.join()函数被一个名字叫runBlocking的包围了,而Job.start()和Job.cancel都没有;Job.cancelAndJoin()前面被一个特殊的关键词suspend修饰了,这有什么用呢?
其实通过查看源码,Job.join()也被suspend修饰了,所以这是一个suspend(挂起)函数,挂起函数必须在协程中或者挂起函数中使用,因为调用了Job.join(),Job.cancelAndJoin()也必须加上suspend声明。事实上,要启动协程,必须至少有一个挂起函数。
协程及协程挂起:
协程是通过编译技术实现的,不需要虚拟机VM/操作系统OS的支持,通过相关代码来生效
协程的挂起几乎无代价,无需上下文切换或涉及OS
协程不能在随机指令中挂起,只能在挂起点挂起(调用标记函数)!
子协程
上面我们都是在线程中开启一个协程,同样在协程中我们也能开启另一个协程,所以我们再来看下复杂点的例子:
fun main() = runBlocking {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
runBlocking {
delay(2000L)
}
}
最外层的runBlocking为最高级的协程 (一般为主协程), 其他协程如launch {} 因为层级较低能跑在runBlocking里。runBlocking的最大特点就是它的delay()可以阻塞当前的线程,和Thread.sleep()有着相同的效果。打印的日志同第一个示例。
Job类中会存储子协程的集合:
public val children: Sequence<Job>
同样也提供了取消全部子协程的方法:
public fun Job.cancelChildren() {
children.forEach { it.cancel() }
}