android kotlin 协程(四) 协程间的通讯
学完本篇你将会了解到:
- channel
- produce
- actor
- select
先来经过上一篇的简略案例回忆一下挂起于康复:
fun main() {
val waitTime = measureTimeMillis {
runBlocking<Unit> {
println("main start") // 1 // 调度前
launch {
println("launch 1 start") // 2 // 调度后(履行前)
delay(1000) // 推迟1s (不会堵塞兄弟协程)
println("launch 1 end") // 3
}
println("main mid") // 4 // 调度前
launch {
println("launch 2 start") // 5 // 调度后履行
delay(500) // 推迟0.5s (不会堵塞兄弟协程)
println("launch 2 end") // 6
}
println("main end") // 7 // 调度前
}
}
println("等候时间:${waitTime}")
}
经过上一篇咱们知道了在协程中,
是会先履行调度前的代码,然后会履行调度后的代码, 直到调度后的时分,才会真实的履行到协程体中
所以这段代码的履行顺序为:
1,4,7,2,5,6,3
launch{} 中的lambda表达式 是一个suspend 函数标记的,所以一直是异步的,并不会堵塞兄弟协程
所以等候时间 约等于 1000
这儿为什么说是约等于呢? 因为创立协程等一系列操作会稍微耗时一点,直接取整即可!
Channel
send / receive
channel是用来协程之前通讯的,例如现在有一个需求,B协程需要运用A协程中的某个值,那么就用到了channel
先来看个最简略的比如
能够看出,A协程能够完结发送,而且B协程也能够完结承受
假如说A协程是一个网络接口,会回来数据,此时B协程是否还会等候A协程数据回来呢?
能够看出,即使是A协程会推迟2s,那么B协程也会等候A协程回来
假如说,A协程现在有3条数据要发送,B协程是否会承受3条呢?
那么就要介绍 Channel()的第一个参数了:
- capacity 通道容量
channel 类似于一个堵塞队列(BlockingQueue), 默认是只缓存1条数据,只要不取,那么新的数据就无法加入到容器中
当send第二条数据的时分, 发现并没有receive() 来取第二条数据,所以就会出现一直挂起的作用
此时咱们只需要让channel通道中容量变大,多寄存几条数据即可
例如这样:
假如说,咱们不想改动通道容量的巨细,而且, 还要不让他挂起,那么就要介绍 channel的第二个参数了:
- onBufferOverflow
从姓名也能够看出,这是缓冲区溢出战略,一共有三种状况
- BufferOverflow.SUSPEND: 挂起战略,当send不进去数据的时分,一直挂起,等候 receive() [默认]
- BufferOverflow.DROP_OLDEST: 当要溢出的时分,删去缓冲区中最旧的值
- BufferOverflow.DROP_LASTEST: 当要溢出的时分,删去缓冲区中最新的值
仍是上面的比如,咱们将容量设置为1, 往 channel中send 3条数据来看看作用
BufferOverflow.DROP_OLDEST | BufferOverflow.DROP_LASTEST |
---|---|
现在这些代码应该很好理解!
trySend / tryReceive
在新版的channel更新的api中,还增添了一系列 tryXXapi
来看一段代码:
-
trySend() 测验向channel中发送数据。这个函数会当即回来一个成果,标明是否成功将元素发送到通道中。假如通道已满,它会当即回来一个
Failure
类型的成果,不然会回来一个Success
类型的成果。一般来说,生产者协程运用trySend
函数来测验将数据发送到通道中,不会堵塞协程,一起能够经过回来成果来判断是否成功发送数据。 -
tryReceive() 这个函数会当即回来一个成果,标明是否成功从通道中接纳到元素。假如通道已空,它会当即回来一个
Failure
类型的成果,不然会回来一个Success
类型的成果。一般来说,消费者协程运用tryReceive
函数来测验从通道中接纳数据,不会堵塞协程,一起能够经过回来成果来判断是否成功接纳数据。
// TODO =================== trySend / tryReceive ======================
fun main() = runBlocking<Unit> {
// 用来协程间的通讯
val channel = Channel<String>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
println("main start")
launch { // A协程
// channel.close()
val trySend = channel.trySend("A协程发送数据 1")
if (trySend.isSuccess) {
println("channel 发送成功")
} else if (trySend.isClosed) {
println("channel 封闭了")
} else if (trySend.isFailure) {
println("channel 发送失利")
}
}.join() // A协程有必要履行完,通道有数据了之后才能取
val tryReceive = channel.tryReceive()
if (tryReceive.isSuccess) {
println("tryReceive 接纳到了数据:${tryReceive.getOrNull()}")
} else if (tryReceive.isClosed) {
println("tryReceive 封闭了")
} else if (tryReceive.isFailure) {
println("tryReceive 发送失利")
}
println("main end")
}
运转成果:
还有一些比较老的办法例如:
- offer / poll 等一些筛选的办法就不说了,
onSend / onReceive
还有最终一种发送,获取数据的方法,这种方法是经过select 选择器来完成的,先来看代码
//// TODO =================== onSend / onReceive ======================
fun main() = runBlocking<Unit> {
// 用来协程间的通讯
val channel = Channel<String>(capacity = 5, onBufferOverflow = BufferOverflow.SUSPEND)
println("main start")
launch { // A 协程 发送数据
channel.send("send发送数据 ")
channel.trySend("trySend发送数据")
}
// select 接纳数据 默认会挂起,等候数据回来
select {
channel.onReceive {
println("onReceive:$it")
}
}
println("result ${channel.receive()}")
channel.invokeOnClose {
println("channel close ")
}
println("main end")
}
运转成果:
select作用不止这些,现在了解能够承受即可,下面会要点提到!
这儿有一个小知识:
假如看到有这种Select开头的,根本都是要写到select{} 中才能运用
运转成果:
select 下面会提到,这儿就不要点说了.
在实践开发中,对于我来说,channel用的仍是比较少, 我感觉这玩意比较坑,一般情况下,要完成2个协程通讯,我会采用flow
例如这样:
这篇要点不是flow,这儿就不多说了!
produce / actor
produce
produce意为生产者, 其本质便是对协程和channel的一层封装,
它回来一个 ReceiveChannel
对象,这个对象能够用于在其他协程中消费 生产者协程发生的数据。
运用很简略
:
api仍是调用的channel的,对咱们来说只是省略了, new Channel() 的进程,这儿就不多说了
actor
actor 与produce正好相反
actor本质也是对协程与channel的封装, 它会回来一个SendChannel
对象,这个对象用来给协程体发送数据
select
定义: 一旦某个挂起函数回来成果,select
结构就会当即回来该函数的成果,而其他仍在等候的挂起函数将会被撤销。
注意点: select
只能用于挂起函数(即运用 suspend
润饰的函数)。另外,select
的选择器表达式中每个分支都应该回来相同类型的值,不然会编译报错。
简略的说便是, select 能够找到哪一个协程履行最快, 吧履行最快的成果回来,其他履行慢的,或者没有履行的协程悉数封闭!
假设咱们现在有一个实践的运用场景:
在实践开发中,咱们需要恳求接口, 恳求接口的之前需要判断是否有缓存,
假如有缓存,就运用缓存数据
但是,假如恳求接口比读取缓存数据还快,那么咱们就用恳求出来的数据
一般情况下缓存永远比恳求数据快,这儿就举个比如
select不仅能够监听 async的回来, 还有许多用处,例如能够监听协程是否履行完, 而且回来最快履行完的协程
来看看代码:
完好代码
下篇预告:
- suspendCoroutine{}
- suspendCancellableCoroutine{}
- suspend 与 continuation与状况机器
- 不经过协程运转 suspend函数
原创不易,您的点赞便是对我最大的协助!