Kotlin实战指南十四:协程启动模式

转载请标明出处:https://blog.csdn.net/zhaoyanjun6/article/details/96008400
本文出自【赵彦军的博客】


协程启动

说了这么多线程,原因嘛,毕竟大家对它是最熟悉的。协程的 API 设计其实也与之一脉相承,我们来看一段最简单的启动协程的方式:

GlobalScope.launch {
    //do what you want
}

那么这段代码会怎么执行呢?我们说过,启动协程需要三样东西,分别是 上下文启动模式协程体协程体就好比 Thread.run 当中的代码,自不必说。

本文将为大家详细介绍 启动模式。在 Kotlin 协程当中,启动模式是一个枚举:

public enum class CoroutineStart {
    DEFAULT,
    LAZY,
    @ExperimentalCoroutinesApi
    ATOMIC,
    @ExperimentalCoroutinesApi
    UNDISPATCHED;
}
模式 功能
DEFAULT 立即执行协程体
ATOMIC 立即执行协程体,但在开始运行之前无法取消
UNDISPATCHED 立即在当前线程执行协程体,直到第一个 suspend 调用
LAZY 只有在需要的情况下运行

DEFAULT

四个启动模式当中我们最常用的其实是 DEFAULTLAZY

DEFAULT是饿汉式启动,launch 调用后,会立即进入待调度状态,一旦调度器 OK 就可以开始执行。我们来看个简单的例子:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch(start = CoroutineStart.DEFAULT) {
            log(1)
            delay(1000)
            log(3)
        }

        log(2)
    }
}

输出结果是:

E/zhaoyanjun:: 2 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-2
E/zhaoyanjun:: 3 DefaultDispatcher-worker-2

LAZY

LAZY 是懒汉式启动,launch后并不会有任何调度行为,协程体也自然不会进入执行状态,直到我们需要它执行的时候。这其实就有点儿费解了,什么叫我们需要它执行的时候呢?就是需要它的运行结果的时候,launch调用后会返回一个 Job实例,对于这种情况,我们可以:

  • 调用 Job.start,主动触发协程的调度执行
  • 调用 Job.join,隐式的触发协程的调度执行

所以这个所谓的”需要“,其实是一个很有趣的措辞,后面你还会看到我们也可以通过 await来表达对 Deferred的需要。这个行为与 Thread.join不一样,后者如果没有启动的话,调用 join 不会有任何作用

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
            log(1)
            delay(1000)
            log(2)
        }

        log(3)
        job.start()
        log(4)
    }
}

执行结果:

E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-1
E/zhaoyanjun:: 2 DefaultDispatcher-worker-1

ATOMIC

ATOMIC 只有涉及 cancel 的时候才有意义,cancel本身也是一个值得详细讨论的话题,在这里我们就简单认为 cancel 后协程会被取消掉,也就是不再执行了。那么调用cancel的时机不同,结果也是有差异的,例如协程调度之前、开始调度但尚未执行、已经开始执行、执行完毕等等。

为了搞清楚它与 DEFAULT 的区别,我们来看一段例子:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
            log(1)
            delay(1000)
            log(2)
        }

        log(3)
        job.cancel()
        log(4)
    }
}

执行结果:

E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main
E/zhaoyanjun:: 1 DefaultDispatcher-worker-1

我们创建了协程后立即cancel,但由于是 ATOMIC模式,因此协程一定会被调度,因此 1、3、4 一定都会输出。
从输出结果看不出什么,现在我们用 DEFAULT 模式来执行一遍

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
            log(1)
            delay(1000)
            log(2)
        }

        log(3)
        job.cancel()
        log(4)
    }
}

执行结果:

E/zhaoyanjun:: 3 main
E/zhaoyanjun:: 4 main

对应的,如果是DEFAULT 模式,在第一次调度该协程时如果 cancel就已经调用,那么协程就会直接被 cancel 而不会有任何调用。

需要注意的是,cancel 调用一定会将该 job 的状态置为 cancelling,只不过ATOMIC模式的协程在启动时无视了这一状态。

我们使用线程的时候,想要让线程里面的任务停止执行也会面临类似的问题,但遗憾的是线程中看上去与 cancel 相近的 stop 接口已经被废弃,因为存在一些安全的问题。不过随着我们不断地深入探讨,你就会发现协程的 cancel某种意义上更像线程的 interrupt

UNDISPATCHED

有了前面的基础,UNDISPATCHED 就很容易理解了。协程在这种模式下会直接开始在当前线程下执行,直到第一个挂起点,这听起来有点儿像前面的 ATOMIC,不同之处在于 UNDISPATCHED 不经过任何调度器即开始执行协程体。当然遇到挂起点之后的执行就取决于挂起点本身的逻辑以及上下文当中的调度器了。

附录

log 代码

 fun log(mes: Int) {
    Log.e("zhaoyanjun:", "$mes ${Thread.currentThread().name}")
 }

参考资料

破解 Kotlin 协程(2) - 协程启动篇 https://www.bennyhuo.com/2019/04/08/coroutines-start-mode/


个人微信号:zhaoyanjun125 , 欢迎关注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章