Kotlin协程笔记
2023-04-22 本文已影响0人
丹丹无敌
协程与线程关系
- 协程是轻量级线程、比线程耗费资源少 这话虽然是官方说的,但我觉得有点误导的作用,协程是语言层面的东西,线程是系统层面的东西,两者没有可比性。
协程就是一段代码块,既然是代码那就离不开CPU的执行,而CPU调度的基本单位是线程。 - 协程是线程框架
协程解决了异步编程时过多回调的问题,既然是异步编程,那势必涉及到不同的线程。Kotlin 协程内部自己维护了线程池,与Java 线程池相比有些优化的地方。在使用协程过程中,无需关注线程的切换细节,只需指定想要执行的线程即可,从对线程的封装这方面来说这说话也没问题。 - 协程效率高于线程 与第一点类似,协程在运行方面的高效率其实换成回调方式也是能够达成同样的效果,实际上协程内部也是通过回调实现的,只是在编译阶段封装了回调的细节而已。因此,协程与线程没有可比性。
suspend修饰的挂起于协程的意义
- 当函数被suspend 修饰时,表明协程执行到此可能会被挂起,若是被挂起那么意味着协程将无法再继续往下执行,直到条件满足恢复了协程的运行。
fun main(array: Array<String>) {
GlobalScope.launch {
println("before suspend")//①
testSuspend()//挂起函数②
println("after suspend")//③
}
}
执行到②时,协程被挂起,将不会执行③,直到协程被恢复后才会执行③。
- 如果将suspend 修饰的函数类型看做一个整体的话:
suspend () -> T
无参,返回值为泛型。
Kotlin 里定义了一些扩展函数,可用来开启协程。
- suspend 修饰的函数类型,当调用者实现其函数体时,传入的实参将会继承自SuspendLambda
如何开启一个原始的协程
launch/async/runBlocking 如何开启协程
纵观这几种主流的开启协程方式,它们最终都会调用到:
#CoroutineStart.kt
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
无论走哪个分支,都是调用block的函数,而block 就是我们之前说的被suspend 修饰的函数。
以DEFAULT 为例startCoroutineCancellable接下来会调用到IntrinsicsJvm.kt里的:
#IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
)
该函数带了俩参数,其中的receiver 为接收者,而completion 为协程结束后调用的回调。
为了简单,我们可以省略掉receiver。
刚好IntrinsicsJvm.kt 里还有另一个函数:
#IntrinsicsJvm.kt
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit>
createCoroutineUnintercepted 为 (suspend () -> T) 类型的扩展函数,因此只要我们的变量为 (suspend () -> T)类型就可以调用createCoroutineUnintercepted(xx)函数。
查找该函数的使用之处,发现Continuation.kt 文件里不少扩展函数都调用了它。
如:
#Continuation.kt
//创建协程的函数
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit> =
SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
其中Continuation 为接口:
#Continuation.kt
interface Continuation<in T> {
//协程上下文
public val context: CoroutineContext
//恢复协程
public fun resumeWith(result: Result<T>)
}
Continuation 接口很重要,协程里大部分的类都实现了该接口,通常直译过来为:"续体"。
创建完成后,还需要开启协程函数:
#Continuation.kt
//启动协程的函数
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
Kotlin线程池与Java 线程池比对
- Java线程池原理:
- 核心线程+队列+非核心线程。
- 首先使用核心线程执行任务,若是核心线程个数已满,则将任务加入到队列里,核心线程从队列里取出任务执行,若是队列已满,则再开启非核心线程执行任务。
- 协程线程池原理:
- 全局队列(阻塞+非阻塞)+ 本地队列。
- IO 任务分发还有个缓存队列。
- 线程从队列里寻找任务(包括偷)并执行,若是使用IO 分发器,则超出限制的任务将会放到缓存队列里。
- 两者区别:
- Java 线程池开放API,比较灵活,调用者可以根据不同的需求组合不同形式的线程池,没有区分任务的特点(阻塞/非阻塞)。
- 协程线程池专供协程使用,区分任务特点,进而进行更加合理的调度。
全局捕获异常
与线程类似,协程也可以全局捕获异常。
//创建处理异常对象
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("handle exception:$exception")
}
fun testException3() {
runBlocking {
//声明协程作用域
var scope = CoroutineScope(Job() + exceptionHandler)
var job1 = scope.launch(Dispatchers.IO) {
println("job1 start")
//异常
1 / 0
println("job1 end")
}
}
}