协程学习(三)协程简单的使用 CoroutineStart

我学习协程的历程

1.协程学习(一)一个最简单的协程例子

2.协程学习(二)协程简单的使用launch async join await

3.# 协程学习(三)协程简单的使用 CoroutineStart

在上一篇文章的结尾的时候,我做了一个试验,那就是在父协程的协程体内创建子协程后,父协程协程体的后续代码块是优先于子协程体内的代码运行的

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {

        launch {
            println("------------子协程启动了-----------")
        }
        println("------------父协程运行-----------")
    }
}
结果: 
------------父协程运行----------- 
------------子协程启动了-----------

但是这里需要注意的是,并不是父协程体内的所有代码都是优先于子协程体内的代码的,那么子协程体是在什么时候开始运行呢,这就是由 CoroutineStart 来控制了,我们先来看看 CoroutineStart 是如何使用的,怎么控制的

他的使用方式如下,

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        launch(start=CoroutineStart.DEFAULT) {
            println("------------子协程启动了-----------")
        }
        println("------------父协程运行-----------")
    }
}

那就是在启动协程时,指定start 参数 ,接下来我们再看一下launch 方法

kotlin 复制代码
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
}

可以看到,这个start 有一个默认的参数,如果我们不指定话,就是 CoroutineStart.DEFAULT ,那么这个 DEFAULT 代表着什么意义呢,这个其实就是一个经常被问到的面试题,在今年8月份面试的过程中也有被问到过.

这个问题是这样的,在启动了一个协程后,调用这个协程的 cancel 取消方法,协程会在哪个时刻被取消

在回答这个问题前,我们需要先了解一下 CoroutineStart 的各个类型,再根据各个类型来回答

CoroutineStart 的类型有 DEFAULT LAZY ATOMIC UNDISPATCHED 这四种,其实每一种类型的介绍在源码上面都写的非常详细

DEFAULT 类型介绍

vbnet 复制代码
Default -- immediately schedules the coroutine for execution according to its context.
If the CoroutineDispatcher of the coroutine context returns true from CoroutineDispatcher.isDispatchNeeded function as most dispatchers do, then the coroutine code is dispatched for execution later, while the code that invoked the coroutine builder continues execution.
Note that Dispatchers.Unconfined always returns false from its CoroutineDispatcher.isDispatchNeeded function, so starting a coroutine with Dispatchers.Unconfined by DEFAULT is the same as using UNDISPATCHED.
If coroutine Job is cancelled before it even had a chance to start executing, then it will not start its execution at all, but will complete with an exception.
Cancellability of a coroutine at suspension points depends on the particular implementation details of suspending functions. Use suspendCancellableCoroutine to implement cancellable suspending functions.

大致的意思就是使用了 DEFAULT 这种类型的 CoroutineStart,协程体内会在稍后执行,而创建协程的代码将继续运行,所以在看到了开篇时我们看到的那种现象 ,那么我们在取消的时候出现哪种现象呢,我将开篇的demo 稍微修改,在创建完子协程后就使用job.cancel 方法,取消子协程,代码如下

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        var job=launch(start=CoroutineStart.DEFAULT) {
            println("------------子协程启动了-----------")
            delay(100)
            println("------------子协程执行完毕了-----------")
        }
        println("------------父协程运行-----------${job.cancel()}")
    }
}
结果:
------------父协程运行-----------kotlin.Unit

Process finished with exit code 0

结合 DEFAULT 的介绍,由于子协程会在稍后启动,那么在我们调用 cancel 时,由于子协程就没有启动,整个子协程的任务全部被取消了, 我们将上面的例子再修改一下,在创建完子协程后 使用delay 让父协程挂起1毫秒,然后再去cancel子协程

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        var job=launch(start=CoroutineStart.DEFAULT) {
            println("------------子协程启动了-----------")
            delay(100)
            println("------------子协程执行完毕了-----------")
        }
        delay(1)
        println("------------父协程运行-----------${job.cancel()}")
        delay(5)
        // active 代表着被启动了,并且没有完成或者取消状态
        println("子协程的状态是 isActive:${job.isActive}  isCancelled:${job.isCancelled}")
        delay(100)
        println("子协程的状态是 isActive:${job.isActive}  isCancelled:${job.isCancelled}")
    }
}

结果:
------------子协程启动了-----------
------------父协程运行-----------kotlin.Unit
子协程的状态是 isActive:false  isCancelled:true
子协程的状态是 isActive:false  isCancelled:true

Process finished with exit code 0

可以看到只要父协程挂起后,子协程马上就会被执行,此时再调用子协程是无法取消正在运行的协程任务,但是会使协程在下一个挂起点或者结束点被修改为取消,

现在我们来总结一下 CoroutineStart.DEFAULT 启动的协程被取消后的逻辑是如何运行的

1: 在协程被创建,但是并没有被启动时调用取消方法,则协程的状态变为取消状态,并且协程体内的代码无法执行

2: 如果在协程运行后调用协程的取消方法,协程会将 isActive 状态标记为false,isCancelled标记为 true,并且会在下一次的挂起点停止执行

ATOMIC 类型介绍

ATOMIC 的类型比较有意思,还是与 DEFAULT 一样,先看一下他的官方介绍

vbnet 复制代码
Atomically (i.e., in a non-cancellable way) schedules the coroutine for execution according to its context. This is similar to DEFAULT, but the coroutine cannot be cancelled before it starts executing.
Cancellability of coroutine at suspension points depends on the particular implementation details of suspending functions as in DEFAULT.

大致意思如下,那就是在协程启动前是无法取消他的,其他表现与 DEFAULT 是一致的

既然是在启动前不能被取消,那么我们还是使用 DEFAULT 中创建后就取消的代码来看一下

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        var job=launch(start=CoroutineStart.ATOMIC) {
            println("------------子协程启动了-----------")
            delay(100)
            println("------------子协程执行完毕了-----------")
        }
        println("------------父协程运行-----------${job.cancel()}")
    }
}
------------父协程运行-----------kotlin.Unit
------------子协程启动了-----------

Process finished with exit code 0

现在我们来总结一下 CoroutineStart.ATOMIC 启动的协程被取消后的逻辑是如何运行的

1:协程在被创建后启动前无法被取消

2:如果在协程运行后调用协程的取消方法,协程会将 isActive 状态标记为false,isCancelled标记为 true,并且会在下一次的挂起点停止执行

LAZY 类型介绍

sql 复制代码
Starts the coroutine lazily, only when it is needed.
See the documentation for the corresponding coroutine builders for details (like launch and async).
If coroutine Job is cancelled before it even had a chance to start executing, then it will not start its execution at all, but will complete with an exception.

使用 LAZY 启动的协程,只有我们手动的启动后他才会被执行,如果我们将他启动后,他的取消方式与 DEFAULT 是一致的,

先来看看启动 LAZY 的方式有哪些

1: start // 类似于 DEFAULT 协程体会在调用start 方法后稍后执行,并且不影响该方法的后续代码的执行

2: join //在上一篇文章介绍过join 方法,但是使用了LAZY 启动模式时,会启动子协程,并且将当前代码断挂起

3:await // 与 join类型,但是能获取到返回结果

使用这3种方式都可以启动 LAZY 方式的协程,具体如何选择需要结合实际场景来判断

我们来演示 LAZY 有哪些不同之处

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        var job=launch(start=CoroutineStart.LAZY) {
            println("------------子协程启动了-----------")
            delay(100)
            println("------------子协程执行完毕了-----------")
        }
        println("------------父协程运行-----------${job.cancel()}")
        job.join()
        println("子协程的状态是 isActive:${job.isActive}  isCancelled:${job.isCancelled}")
    }
}

结果:
------------父协程运行-----------kotlin.Unit
子协程的状态是 isActive:false  isCancelled:true

Process finished with exit code 0

我们在没有启动子协程时就先将子协程取消,那么子协程就不会被启动,

现在我们来总结一下 CoroutineStart.LAZY 启动的协程被取消后的逻辑是如何运行的

1:我们可以使用 start join await 等方式来启动 LAZY 的协程,如果在协程没有启动前就调用了 LAZY ,协程就不会被启动

2: 如果在协程运行后调用协程的取消方法,协程会将 isActive 状态标记为false,isCancelled标记为 true,并且会在下一次的挂起点停止执行

UNDISPATCHED 类型介绍

vbnet 复制代码
Immediately executes the coroutine until its first suspension point in the current thread similarly to the coroutine being started using Dispatchers.Unconfined. However, when the coroutine is resumed from suspension it is dispatched according to the CoroutineDispatcher in its context.
This is similar to ATOMIC in the sense that coroutine starts executing even if it was already cancelled, but the difference is that it starts executing in the same thread.
Cancellability of coroutine at suspension points depends on the particular implementation details of suspending functions as in DEFAULT.
Unconfined event loop
Unlike Dispatchers.Unconfined and MainCoroutineDispatcher.immediate, nested undispatched coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited nesting.

UNDISPATCHED 这种方式也非常的有意思,从字面意思是不调度,给我的感觉这个参数应该是给 Dispatchers 来设置的,这里的意思就是创建完协程后,不使用调度器来调度,直接同步执行,直到遇到下一个挂起点才会按照设置的调度器来调度执行,

那么我们就给子协程指定 Dispatchers.IO ,同时指定 CoroutineStart.UNDISPATCHED来看看结果

kotlin 复制代码
@JvmStatic
fun main(a:Array<String>) {
    runBlocking {
        var job=launch(context = Dispatchers.IO,start=CoroutineStart.UNDISPATCHED) {
            println("------------子协程启动了-----------${Thread.currentThread().name}")
            delay(100)
            println("------------子协程执行完毕了-----------")
        }
        println("子协程的状态是 isActive:${job.isActive}  isCancelled:${job.isCancelled}")
        println("------------父协程运行-----------${job.cancel()}")
        println("子协程的状态是 isActive:${job.isActive}  isCancelled:${job.isCancelled}")
    }
}

结果:
------------子协程启动了-----------main
子协程的状态是 isActive:true  isCancelled:false
------------父协程运行-----------kotlin.Unit
子协程的状态是 isActive:false  isCancelled:true

Process finished with exit code 0

在这段代码中,子协程的创建我使用了 Dispatchers.IO ,但是在子协程体中的线程仍然是在主线程中执行的,并且子协程体的代码块的执行时机是优先于父协程体的后续代码块的,

现在我们来总结一下 CoroutineStart.UNDISPATCHED 启动的协程被取消后的逻辑是如何运行的

1: 使用 UNDISPATCHED 启动的协程会获取父协程或者外部线程来执行,直到遇到第一个挂起点后才会按照挂起点执行的线程逻辑来执行后续代码,这就意味着在创建后,子协程无法被父协程取消

2: 如果在协程运行后调用协程的取消方法,协程会将 isActive 状态标记为false,isCancelled标记为 true,并且会在下一次的挂起点停止执行

相关推荐
500了4 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵5 小时前
Android Debug Bridge(ADB)完全指南
android·adb
小雨cc5566ru10 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng11 小时前
android 原生加载pdf
android·pdf
hhzz11 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒12 小时前
XSS基础
android·web安全
勿问东西14 小时前
【Android】设备操作
android
五味香14 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
图王大胜16 小时前
Android Framework AMS(01)AMS启动及相关初始化1-4
android·framework·ams·systemserver
工程师老罗18 小时前
Android Button “No speakable text present” 问题解决
android