Kotlin协程:启动模式

前言

我们看launch、async 构造函数需要传入一个协程上下文、协程启动模式、协程作用域。

本文我们重点介绍启动模式

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,//上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,//启动模式
    block: suspend CoroutineScope.() -> Unit	//作用域
): Job {
  ......
}
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,//上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,//启动模式
    block: suspend CoroutineScope.() -> T	//作用域
): Deferred<T> {
   
}

1、使用启动模式

使用启动模式只需要在构造传参:start=启动模式

CoroutineStart.DEFAULT 是默认启动模式,可以不用设置

launch(){//......}//默认CoroutineStart.DEFAULT
launch(start = CoroutineStart.DEFAULT){//......}
launch(start = CoroutineStart.ATOMIC){//......}
launch(start = CoroutineStart.LAZY){//......}
launch(start = CoroutineStart.UNDISPATCHED){//......}

async() {//......}//默认CoroutineStart.DEFAULT
async(start = CoroutineStart.DEFAULT) {//......}
async(start = CoroutineStart.ATOMIC){//......}
async(start = CoroutineStart.LAZY){//......}
async(start = CoroutineStart.UNDISPATCHED){//......}

2、启动模式概况

模式描述
CoroutineStart.DEFAULT协程创建后立即开始调度,在调度前如果协程被取消,就直接进入取消响应的状态,虽然是立即调度,但也有可能在执行前被取消
CoroutineStart.ATOMIC协程创建后立即开始调度,协程执行到第一个挂起点之前不响应取消,因此协程也一定会执行第一个挂起点前的逻辑
CoroutineStart.LAZY协程创建后等待调度,只有协程主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,就直接进入取消响应的状态
CoroutineStart.UNDISPATCHED协程创建后立即在当前函数调用栈中立即执行直到遇到第一个真正挂起的点,因此协程一定会执行

DEFAULT、LAZY两种模式是我们实际开发中比较常用的模式

上面描述说到几个概念:调度、执行、挂起点
要理解上面的启动模式就必须搞清楚什么是调度,什么是执行,什么是挂起点

挂起点

挂起点就是协程中遇到的挂起函数,一个挂起函数就是一个挂起点

我们知道delay是官方的一个挂机函数、suspendTest是我们自定义的一个挂起函数,那么在协程中的挂起点入下:

@Test
fun runBlockTest(): Unit = runBlocking {
    launch {
        delay(1000)//挂起点
        suspendTest()//挂起点
    }
    delay(1000)//挂起点
}

private suspend fun suspendTest(){
    println("我是一个挂起函数")
    delay(1000)
}

挂起点在编译器中也会明细的标记出来
挂起点

调度和执行

这里我用一段代码来说明

@Test
fun coroutineStudy(): Unit = runBlocking {
    val studyJob = launch(start = CoroutineStart.DEFAULT) {
        println("studyJob 协程调度后,执行阶段1")
        println("studyJob 协程调度后,执行阶段2")
        println("studyJob 协程调度后,执行阶段3")
        println("studyJob 协程调度后,执行阶段4")
        println("studyJob 协程调度后,执行阶段5")
        println("----studyJob 执行结束,studyJob 执行结束----")
    }
    println("studyJob 协程调度前1")
    println("studyJob 协程调度前2")
    println("studyJob 协程调度前3")
    println("studyJob 协程调度前4")
    println("studyJob 协程调度前5")
    //挂起点,调度前到执行是很快的1ms都不需要
    delay(1)
    println("studyJob 协程执行结束6")
    println("studyJob 协程执行结束7")
    println("studyJob 协程执行结束8")
    println("studyJob 协程执行结束9")
    println("studyJob 协程执行结束10")
}

调度、执行

执行输出结果:

studyJob 协程调度前1
studyJob 协程调度前2
studyJob 协程调度前3
studyJob 协程调度前4
studyJob 协程调度前5
studyJob 协程调度后,执行阶段1
studyJob 协程调度后,执行阶段2
studyJob 协程调度后,执行阶段3
studyJob 协程调度后,执行阶段4
studyJob 协程调度后,执行阶段5
----studyJob 执行结束,studyJob 执行结束----
studyJob 协程执行结束6
studyJob 协程执行结束7
studyJob 协程执行结束8
studyJob 协程执行结束9
studyJob 协程执行结束10

从代码可知调度到执行的状态切换是很快的,知识挂起了1ms,协程就已经进入了执行阶段

理解了上面的示例代码相信已经理解了调度、执行、挂起点的概念,那么下面对各个启动模式进行介绍。

3、模式 CoroutineStart.DEFAULT

描述:协程创建后立即开始调度,在调度前如果协程被取消,就直接进入取消响应的状态,虽然是立即调度,但也有可能在执行前被取消

示例1:协程立即调度,所以先调度再执行

@Test
fun coroutineStartDefault(): Unit = runBlocking {
    launch(start = CoroutineStart.DEFAULT) {
        //协程只是立即调度,执行打印后输出
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
    }
    //协程立即调度,所以调度打印先输出
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5

示例2:调度前协程被取消,就直接进入取消响应的状态

@Test
fun coroutineStartDefaultCancel(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.DEFAULT) {
    	//协程已经取消,不会执行
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    //调度前取消
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程取消 kotlin.Unit

示例3:调度前挂起1s,让协程进入执行阶段,再取消。
结果:这个时候虽然能取消协程,但是已经没有意义了,协程已经执行完毕了

@Test
fun coroutineStartDefaultDelayCancel(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.DEFAULT) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    delay(1)//调度是比较快的,1ms可能已经调度完成了
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程取消 kotlin.Unit

示例4:调度前挂起1s,让协程进入执行阶段,然后再设置一个挂起点,然后再取消
结果:这个时候协程会执行到挂起点,后续的协程逻辑还是能取消的

@Test
fun coroutineStartDefaultDelayCancel2(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.DEFAULT) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    delay(1)//调度是比较快的,1ms可能已经调度完成了
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程取消 kotlin.Unit

经过上面4个示例相信对DEFAULT模式已经有深刻的认识了

4、模式 CoroutineStart.ATOMIC

描述:协程创建后立即开始调度,协程执行到第一个挂起点之前不响应取消,因此协程也一定会执行第一个挂起点前的逻辑

这个模式创建后立即开始调度,跟DEFAULT一样,这部分不在做说明了,我们重点在不同点的说明

示例1:在调度前取消,协程会执行到第一个挂起点,挂起点后面的被取消了

@Test
fun coroutineStartAtomic(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.ATOMIC) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点前,不响应取消操作
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程取消 kotlin.Unit
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5

示例2:如果延迟1ms,让协程进入执行阶段协程体中挂起50ms,再取消
结果:在第一个挂起点后的协程被取消了,因为挂起50ms,足够协程取消

@Test
fun coroutineStartAtomic2(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.ATOMIC) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        //第一个挂起点前,不响应取消操作
        delay(50)
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    delay(1)
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程取消 kotlin.Unit

示例3:如果延迟100ms,让协程进入执行阶段协程体中挂起50ms,再取消
结果:协程虽然能被取消,但是协程已经执行完毕了,取消没有实际意义

@Test
fun coroutineStartAtomic2(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.ATOMIC) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        //第一个挂起点前,不响应取消操作
        delay(50)
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    delay(100)
    println("协程取消 ${job.cancel()}")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程调度后,执行阶段6
协程调度后,执行阶段7
协程调度后,执行阶段8
协程取消 kotlin.Unit

协程能否有效取消取决于本协程挂起时长与父协程挂起时长

这个模式有一个比较合适的使用场景:有过开发经验的人都知道我们的应用会经常进行网络请求前的一些数据的埋点,那么这个模式就很适合这个场景,比如就算是网络请求失败,我们也要对埋点数据进行处理,那我们就可以使用这个模式进行开发

5、模式 CoroutineStart.LAZY

描述:协程创建后等待调度,只有协程主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,就直接进入取消响应的状态

看名称就知道,这个模式跟懒加载类似,只有主动调度才会执行

lazy模式我们通常使用 start 进行主动调度,因为 start不是一个挂起函数

public interface Job : CoroutineContext.Element {
	//其他内容省略
	
    public fun start(): Boolean
}

示例1:不主动调度,协程不会执行

@Test
fun coroutineStartLazy(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5

示例2:主动调用join进行调度,使用asyn的await也是一样的效果,可以自己测试
结果:join、await是挂起函数,挂起函数后面的代码需要等协程体执行完成再执行

@Test
fun coroutineStartLazyJoin(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    //join是挂起函数,需要等待协程执行完后再恢复执行后续的代码
    println("协程join ${job.join()}")
    println("协程调度前6")
    println("协程调度前7")
    println("协程调度前8")
    println("协程调度前9")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程调度后,执行阶段6
协程调度后,执行阶段7
协程调度后,执行阶段8
协程join kotlin.Unit
协程调度前6
协程调度前7
协程调度前8
协程调度前9

示例2:主动调用start进行调度
结果:start不是挂起函数,调度前的代码还是会执行

@Test
fun coroutineStartLazyStart(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    //start 函数不是挂起函数,后面的代码还是会执行
    println("协程start ${job.start()}")
    println("协程调度前6")
    println("协程调度前7")
    println("协程调度前8")
    println("协程调度前9")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程start true
协程调度前6
协程调度前7
协程调度前8
协程调度前9
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程调度后,执行阶段6
协程调度后,执行阶段7
协程调度后,执行阶段8

示例4:调度前就被取消,就直接进入取消响应的状态

@Test
fun coroutineStartLazyCancelStart(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        println("协程调度后,执行阶段1")
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")//先输出
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    println("协程cancel ${job.cancel()}")
    println("协程start ${job.start()}")
    println("协程调度前7")
    println("协程调度前8")
    println("协程调度前9")
}

//执行输出
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程cancel kotlin.Unit
协程start false
协程调度前7
协程调度前8
协程调度前9

6、模式 CoroutineStart.UNDISPATCHED

描述:协程创建后立即在当前函数调用栈中立即执行直到遇到第一个真正挂起的点,因此协程一定会执行

示例1:协程创建后立即执行,所以先执行协程,再执行调度前的代码

@Test
fun coroutineStartUnDispatched(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.UNDISPATCHED) {
        println("协程调度后,执行阶段1")//先输出
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")//后输出
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
}

//执行输出
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程调度后,执行阶段6
协程调度后,执行阶段7
协程调度后,执行阶段8
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5

示例2:协程立即执行到第一个调度点,如果取消取决于协程体中的挂起时长

@Test
fun coroutineStartUnDispatchedCancel(): Unit = runBlocking {
    val job = launch(start = CoroutineStart.UNDISPATCHED) {
        println("协程调度后,执行阶段1")//先输出
        println("协程调度后,执行阶段2")
        println("协程调度后,执行阶段3")
        println("协程调度后,执行阶段4")
        println("协程调度后,执行阶段5")
        delay(300)//第一个挂起点,会影响
        println("协程调度后,执行阶段6")
        println("协程调度后,执行阶段7")
        println("协程调度后,执行阶段8")
    }
    println("协程调度前1")//后输出
    println("协程调度前2")
    println("协程调度前3")
    println("协程调度前4")
    println("协程调度前5")
    println("协程cancel ${job.cancel()}")
}

//执行输出
协程调度后,执行阶段1
协程调度后,执行阶段2
协程调度后,执行阶段3
协程调度后,执行阶段4
协程调度后,执行阶段5
协程调度前1
协程调度前2
协程调度前3
协程调度前4
协程调度前5
协程cancel kotlin.Unit

注意:这个模式不需要调度,立即执行,这个模式的线程由当前函数调用栈决定

下面用几个示例来说明

示例1:在主线程的环境中运行协程,并指定协程的调度器为IO
结果:协程还是运行在主线程

fun main() = runBlocking<Unit> {
    val job = launch(Dispatchers.IO,start = CoroutineStart.UNDISPATCHED) {
        println("协程调度后,执行阶段1 ${Thread.currentThread().name}")//先输出
        println("协程调度后,执行阶段2 ${Thread.currentThread().name}")
        println("协程调度后,执行阶段3 ${Thread.currentThread().name}")
        println("协程调度后,执行阶段4 ${Thread.currentThread().name}")
        println("协程调度后,执行阶段5 ${Thread.currentThread().name}")
    }
    println("协程调度前1 ${Thread.currentThread().name}")//后输出
    println("协程调度前2 ${Thread.currentThread().name}")
    println("协程调度前3 ${Thread.currentThread().name}")
    println("协程调度前4 ${Thread.currentThread().name}")
    println("协程调度前5 ${Thread.currentThread().name}")
}

//执行输入
协程调度后,执行阶段1 main
协程调度后,执行阶段2 main
协程调度后,执行阶段3 main
协程调度后,执行阶段4 main
协程调度后,执行阶段5 main
协程调度前1 main
协程调度前2 main
协程调度前3 main
协程调度前4 main
协程调度前5 main

示例2:在线程的环境中运行协程,并指定协程的调度器为Main
结果:协程还是运行在线程中

fun main() = runBlocking<Unit> {
    Thread{
        val job = launch(Dispatchers.Main,start = CoroutineStart.UNDISPATCHED) {
            println("协程调度后,执行阶段1 ${Thread.currentThread().name}")//先输出
            println("协程调度后,执行阶段2 ${Thread.currentThread().name}")
            println("协程调度后,执行阶段3 ${Thread.currentThread().name}")
            println("协程调度后,执行阶段4 ${Thread.currentThread().name}")
            println("协程调度后,执行阶段5 ${Thread.currentThread().name}")
        }
        println("协程调度前1 ${Thread.currentThread().name}")//后输出
        println("协程调度前2 ${Thread.currentThread().name}")
        println("协程调度前3 ${Thread.currentThread().name}")
        println("协程调度前4 ${Thread.currentThread().name}")
        println("协程调度前5 ${Thread.currentThread().name}")
    }.start()
}

//执行输入
协程调度后,执行阶段1 Thread-0
协程调度后,执行阶段2 Thread-0
协程调度后,执行阶段3 Thread-0
协程调度后,执行阶段4 Thread-0
协程调度后,执行阶段5 Thread-0
协程调度前1 Thread-0
协程调度前2 Thread-0
协程调度前3 Thread-0
协程调度前4 Thread-0
协程调度前5 Thread-0

看完这两个例子应该理解了上面那句话的含义,CoroutineStart.UNDISPATCHED 根本就不使用调度器,就算你指定了也没有用处的,他的运行线程只跟调用他的线程有关系

所以如果有面试官问你在协程中指定调度器为Dispatchers.IO情况下如何让协程在Main线程中运行,相信你应该有答案了吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值