协程启动模式
协程启动
我们来看一段最简单的启动协程的方式:
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
四个启动模式当中我们最常用的其实是 DEFAULT 和 LAZY。
DEFAULT是饿汉式启动,launch 调用后,会立即进入待调度状态,一旦调度器 OK 就可以开始执行。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch(start = CoroutineStart.DEFAULT) {
println(1)
delay(1000)
println(3)
}
println(2)
}
}
输出结果:
2 main
1 DefaultDispatcher-worker-2
3 DefaultDispatcher-worker-2
LAZY
LAZY
是懒汉式启动,launch
后并不会有任何调度行为,协程体也自然不会进入执行状态,直到我们需要它执行的时候。这其实就有点儿费解了,什么叫我们需要它执行的时候呢?就是需要它的运行结果的时候,launch
调用后会返回一个 Job
实例,对于这种情况,我们可以:
- 调用
Job.start
,主动触发协程的调度执行 - 调用
Job.join
,隐式的触发协程的调度执行
所以这个所谓的”需要“,其实是一个很有趣的措辞,后面你还会看到我们也可以通过 await来表达对Deferred
的需要。这个行为与Thread.join
不一样,后者如果没有启动的话,调用join
不会有任何作用
ATOMIC
ATOMIC
只有涉及 cancel
的时候才有意义,cancel
本身也是一个值得详细讨论的话题,在这里我们就简单认为cancel
后协程会被取消掉,也就是不再执行了。那么调用cancel
的时机不同,结果也是有差异的,例如协程调度之前、开始调度但尚未执行、已经开始执行、执行完毕等等。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
println(1)
delay(1000)
println(2)
}
println(3)
job.cancel()
println(4)
}
}
执行结果:
3 main
4 main
1 DefaultDispatcher-worker-1
我们创建了协程后立即cancel
,但由于是 ATOMIC
模式,因此协程一定会被调度,因此 1、3、4 一定都会输出。
对应的,如果是DEFAULT
模式,在第一次调度该协程时如果cancel
就已经调用,那么协程就会直接被 cancel
而不会有任何调用。
需要注意的是,cancel
调用一定会将该 job
的状态置为 cancelling
,只不过ATOMIC
模式的协程在启动时无视了这一状态。
我们使用线程的时候,想要让线程里面的任务停止执行也会面临类似的问题,但遗憾的是线程中看上去与 cancel
相近的 stop
接口已经被废弃,因为存在一些安全的问题。不过随着我们不断地深入探讨,你就会发现协程的 cancel某种意义上更像线程的 interrupt
。
UNDISPATCHED
有了前面的基础,UNDISPATCHED
就很容易理解了。协程在这种模式下会直接开始在当前线程下执行,直到第一个挂起点,这听起来有点儿像前面的 ATOMIC
,不同之处在于 UNDISPATCHED
不经过任何调度器即开始执行协程体。当然遇到挂起点之后的执行就取决于挂起点本身的逻辑以及上下文当中的调度器了。