Android协程(Coroutines)系列-深入理解CoroutineStart启动模式
???? CoroutineStart简介
其实Android协程(Coroutines)系列-入门文章中的GlobalScope.launch
中的构造方法中需要传入上下文context,启动模式start,协程体block,今日讲解一下协程的CoroutineStart
启动模式.如果不传,默认是 CoroutineStart.DEFAULT
,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 } 复制代码
在 Kotlin 协程当中,CoroutineStart
启动模式是一个枚举:
public enum class CoroutineStart { DEFAULT, LAZY, @ExperimentalCoroutinesApi ATOMIC, @ExperimentalCoroutinesApi UNDISPATCHED; } 复制代码
模式 | 功能 |
---|---|
DEFAULT | 立即进入待调度状态 |
LAZY | 只有需要(start/join/await)时才开始调度 |
ATOMIC | 和DEFAULT类似,且在第一个挂起点前不能被取消 |
UNDISPATCHED | 立即在当前线程执行协程体,直到遇到第一个挂起点(后面取决于调度器) |
???? DEFAULT
四个启动模式当中我们最常用的其实是 DEFAULT
和 LAZY
。
DEFAULT
是饿汉式启动,launch
调用后,会立即进入待调度状态,一旦调度器 OK 就可以开始执行。例子:
suspend fun main() { println(1) val job = GlobalScope.launch { println(2) } println(3) job.join()//等待协程执行完毕 println(4) } 复制代码
打印结果
1 3 2 4 复制代码
上面的代码采用的是默认的启动模式,并且也没有指定调度器,所以调度器也是默认的。在JVM上默认调度器的实现是开一个线程池。
但区区几个线程足以调度成千上万个协程,而且每一个协程都有自己的调用栈,这与纯粹的开线程池去执行异步任务有本质的区别。
????LAZY
LAZY
是懒汉式启动,launch
后并不会有任何调度行为,协程体也自然不会进入执行状态,直到我们需要它执行的时候。
调用
Job.start
,主动触发协程的调度执行调用
Job.join
,隐式的触发协程的调度执行调用
async.await()
suspend fun main() { println(1) val job = GlobalScope.launch(start = CoroutineStart.LAZY) { println(2) } println(3) job.start()//协程开始 println(4) //这里的sleep只是保持进程存活, 目的是为了等待协程执行完 Thread.sleep(5000L) } 复制代码
打印结果
1 3 4 2 复制代码
????ATOMIC
ATOMIC
和DEFAULT
类似,且在第一个挂起点前不能被取消
ATOMIC
只有涉及 cancel(协程能够被取消,是需要一定条件的)
的时候才有意义。那么调用 cancel 的时机不同,结果也是有差异的,例如协程调度之前、开始调度但尚未执行、已经开始执行、执行完毕等等。
???? 注意: kotlinx.coroutines包下的所有挂起函数都是可取消的。这些挂起函数会检查协程的取消状态,当取消时就会抛出CancellationException异常
@ExperimentalCoroutinesApi suspend fun main() { println(1) val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) { println("suspend挂起之前") delay(2000) println(2) } println(3) job.cancel()//协程取消 println(4) } 复制代码
打印结果
1 3 suspend挂起之前 4 复制代码
对于 ATOMIC
模式,我们已经讨论过它一定会被启动,实际上在遇到第一个挂起点之前,它的执行是不会停止的,而 delay
是一个 suspend 函数,这时我们的协程迎来了自己的第一个挂起点,恰好 delay
是支持 cancel 的,因此后面的 2 将不会被打印。
????UNDISPATCHED
协程在这种模式下会直接开始在当前线程下执行,直到第一个挂起点,这听起来有点儿像前面的 ATOMIC
,不同之处在于 UNDISPATCHED
不经过任何调度器即开始执行协程体。当然遇到挂起点之后的执行就取决于挂起点本身的逻辑以及上下文当中的调度器了。
@ExperimentalCoroutinesApi suspend fun main() { println(1) val job = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { println("suspend挂起之前") delay(2000) println(2) } println(3) job.join() println(4) } 复制代码
打印结果
1 suspend挂起之前 3 2 4 复制代码
协程启动后会立即在当前线程执行,因此 1、"suspend挂起之前" 会连续在同一线程(main线程)中执行,delay
是挂起点,因此 2 会等 2000ms 后再次调度,这时候 3 执行,join
要求等待协程执行完,因此等 2 输出后再执行 4。
作者:小Fuคิดถึง
链接:https://juejin.cn/post/7021020346354499598