阅读 297

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

ATOMICDEFAULT类似,且在第一个挂起点前不能被取消

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

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐