android kotlin 协程(一)
config:
-
system: macOS
-
android studio: 2022.1.1 Electric Eel
-
gradle: gradle-7.5-bin.zip
-
android build gradle: 7.1.0
-
Kotlin coroutine core: 1.6.4
前语:最近体系的学习了一遍协程, 计划经过10篇左右blog来记载一下我对协程的了解, 从最简略的 runBlocking开端; 到最终 suspend和continuation的关系等等
tips:前面几篇全都是协程的根本运用,没有源码,等后面对协程有个根本了解之后,才会简略的分析一下源码!
学习我这个系列的协程, 只需求记住一点, suspend函数 永久不会堵塞main线程履行! 永久是异步的!
看完本篇你将会学到哪些知识:
- runBlocking()
- CoroutineScope#launch()
- CoroutineScope#async()
- Job的常用办法
- 协程状况[isActive,isCancelled,isCompleted]
runBlocking
定义: runBlocking 会堵塞线程来等候自己子协程履行完, 而且关于不是子协程的效果域,也会尽量的去履行,
首要来了解一下什么是自己的子协程
通常咱们经过
- CoroutineScope.launch{}
- CoroutineScope.async{}
来敞开一个协程,由于当时是在CoroutineScope效果域中,所以直接launch / async 即可
这段代码能够看出,runBlocking 会等候子协程全部履行完,然后在结束任务,由于协程都是异步的,
所以会先履行协程之外的代码,然后再履行协程中的代码
能够在协程中增加一些睡觉操作再来测验一下
能够看出,仍是能够正常的履行完所有代码
现在解释完了定义中的前半句话: runBlocking 会堵塞线程来等候自己子协程履行完, 而且关于不是子协程的效果域,也会尽量的去履行,
再来看一下后半句话:
能够看出,经过自定义coroutine 和 GlobalScope,来创建的协程照样能够履行出来
那么在他们之中略微增加一点逻辑会怎么样?
能够看出,一旦增加了一点逻辑, runBlocking是不会等候非子协程的效果域
假如想让runBlocking等候非子协程履行完,那么只需求调用Job.#join() 即可
例如这样:
join()办法目前能够了解为: 等候当时协程履行完 在往下履行其他代码,
一旦调用了join()办法,那么协程就变成了同步的,那么这块代码总共履行需求4s
由于协程1并没有join, 所以协程1仍是异步的,
协程2调用了join,所以在履行协程2的过程中,协程1也在履行.
所以协程1,与协程2的履行时间为2s
tips: 在开发中不建议运用runBlocking,由于会堵塞主线程,堵塞主线程的时间,用来子协程的履行..
敞开协程两种不同的办法
在上面代码中咱们提到了,敞开协程有2种办法
- CoroutineScope#launch{}
- CoroutineScope#async{}
先来看相同点:
相同点便是无论是哪种办法,都会履行里面的代码
那么这两种办法有什么区别呢?
-
launch无法回来数据, async能够回来成果
回来的成果经过 Deferred#await()
来获取,而且调用Deferred#await()
的时分,会等候async{} 履行完结之后在往下履行,就和Job#join
相同,不过await()
有回来成果
运用await的时分有一个留意点:
那么也能够看到,launch{} 与 async{} 的回来值也有所不同:
- launch{} 回来 Job
- async{} 回来Deferred
其实实质上来说,async 回来的也是Job,不过仅仅Job的子类Deferred而已,Deferred仅仅对回来值等一些操作的封装
那么Job是用来干什么的呢?
Job是用来办理协程的生命周期的, 例如刚才提到的 Job.join() 就能够让协程 “当即履行”
launch{} 和 async{} 捕获反常的办法也不同,这个等下一篇专门聊反常的时分在具体讲解
Job.cancel 撤销协程
协程比线程好用的一点便是协程能够自己办理生命周期, 而线程则不能够
这里需求留意的是,假如协程体中还在履行,可是外部现已撤销了,那么则会throw反常出来
JobCancellationException
fun main() = runBlocking<Unit> {
println("main start")
val job = launch {
try {
(0..100).forEachIndexed { index, _ ->
delay(1000)
println("launch $index")
}
} catch (e: Exception) {
println("协程被撤销了 $e")
}
}
// 协程履行完结监听
job.invokeOnCompletion {
println("协程履行结束${it}")
}
delay(5500)
// 撤销协程
job.cancel()
println("main end")
}
Job#invokeOnCompletion: 协程完结回调
运行成果:
main start
launch 0
launch 1
launch 2
launch 3
launch 4
main end
协程被撤销了 kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@76a4d6c
协程履行结束kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelled}@76a4d6c
coroutine的3种状况
coroutine的三种状况都是经过Job来办理的:
- isActive 是否是活泼状况
- isCancelled 是否撤销
- isCompleted 是否完结
先来看看正常流程履行的代码:
咱们知道协程始终是异步履行的,在履行printlnJob的时分,协程体中的代码还没有真实的履行
所以此刻处于活泼状况,而且协程没有被履行完
假如咱们在协程履行完结的回调中调用
那么此刻,协程体中的代码现已履行完了,那么此刻便是非活泼状况
还剩一个Job#isCancelled 这个办法比较简略,简略的说便是是否调用了Job.cancel()
可是这里有一个特别奇怪的点,明明现已调用Job#cancel() 来撤销协程,而且协程体中的代码也没履行,可是为什么还显现协程没有履行完呢?
由于Job#cancel() 并不是suspend函数,不是suspend函数就没有康复功能,这行文字可能看的有一点迷惑,先不必管什么挂起于康复,现在只需求知道
咱们调用cancel() 的时分会紧跟着一个,Job#join() 即可
或许直接调用Job.cancelAndJoin() 即可
挂起康复,这4个字我了解了10天左右,不可能经过本篇就讲清楚,现在只需求会调用这些api,即可!!
那么问题就来了,这个状况有什么用呢?
先来看一段代码:
能够惊讶的发现,这段代码无论怎么都cancel不掉.好像是失效了相同
那么处理这个问题,就能够检测协程是否是活泼状况,例如这样
Job也提供了一个办法: Job#ensureActive()
ensureActive() 实质也是经过isActive判别,不同的是,当撤销的时分能够捕获到撤销的反常,然后来处理对应的事件
图片地址: gitee.com/lanyangyang…
回忆一下本篇:
本篇咱们讲解了runBlocking, 这个函数会帮咱们堵塞主线程, 堵塞住线程的时分会等候内部的子协程全部履行完
还聊了最基础的怎么敞开一个协程, launch / async 以及他们的相同点和不同点
最终引出了协程生命周期办理者Job, 讲解了Job常用的办法,以及job的3种状况
办法名 | 效果 | 补充 |
---|---|---|
join() | 当即康复协程体履行 | 等候协程体履行完结,在履行后续代码 |
cancel() | 撤销协程 | ,假如撤销时,协程体还在履行,这throw JobCancellationException,这个反常不会上报,会自行处理 |
invokeOnCompletion() | 协程体履行完结回调 | |
isActive | 协程体是否是活泼状况 | |
isCancelled | 协程体是否被撤销 | |
isCompleted | 协程体是否履行完结 |
完好代码
下一篇预告:
-
CoroutineDispatcher // 协程调度器 用来切换线程
-
CoroutineName // 协程姓名
-
CoroutineStart // 协程启动形式
-
CoroutineException // launch / async 捕获反常
-
GlobalCoroutineException // 全局捕获反常
原创不易,您的点赞便是我最大的支撑!