前言
kotlin
fun main() {
runBlocking {
val flow = flow {
emit("emit")
}
flow.collect{
log("collect$it")
}
}
}
上游和下游属于同一个线程里。
- 操作符,即函数
- 上游,通过构造操作符创建
- 下游,通过末端操作符构建
只有下游才能通知上游放水,Flow属于冷流。生产数据的模块将生产过程封装到flow的上游里,最终创建了flow对象。
Channel核心原理与使用场景
Flow比较被动,在没有收集数据之前,上下游互不感知,管道并没有建立起来。
场景:需要将管道提前建立起来,在任何时候都可以在上游生产数据,在下游取数据,此时上下游可以感知的。
kotlin
fun main() {
// 提前建立通道/管道
val channel = Channel<String>()
GlobalScope.launch {
// 上游放水
delay(200)
val data = "放水了"
log("上游:$data")
channel.send(data)
}
GlobalScope.launch{
val data = channel.receive()
log("下游收到:$data")
}
// 防止父线程过早退出
Thread.sleep(250)
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] 上游:放水了
[Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main]] 下游收到:放水了
先建立管道;往管道里放数据;从管道里取数据;
- 创建Channel
- 往Channel里放数据(生产)
- 从Channel里取数据(消费)
与Flow不同,生产者、消费者可以往Channel里存放/取出数据,只是能进行有效的存放,能否成功取出需要根据Channel状态确定。
Channel最大特点:
- 生产者、消费者访问Channel线程安全的,不管生产者和消费者在哪个线程,他们都能线程安全的存取数据
- 数据只能被消费一次,上游发送了一条数据,只要下游消费了数据,则其他下游将不会拿到此数据。
Flow切换线程的始末
场景:需要在flow里进行耗时操作(网络请求),外界拿到flow对象后等待收集数据即可。
kotlin
fun main() {
runBlocking {
val flow = flow {
thread {
Thread.sleep(3000)
// 这里编译不过
emit("emit")
}
}
}
}
emit是挂起函数,需要在协程作用域里调用。
kotlin
fun main() {
runBlocking {
val flow = flow {
val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
coroutineScope.launch {
Thread.sleep(3000)
// 这里运行报错:检测到在另一个线程里发射数据,这种行为不是线程安全的因此被禁止了
emit("emit")
}
}
flow.collect {
log("collect:$it")
}
}
// 防止父线程过早退出
Thread.sleep(3500)
}
kotlin
if (emissionParentJob !== collectJob) {
error(
"Flow invariant is violated:\n" +
"\t\tEmission from another coroutine is detected.\n" +
"\t\tChild of $emissionParentJob, expected child of $collectJob.\n" +
"\t\tFlowCollector is not thread-safe and concurrent emissions are prohibited.\n" +
"\t\tTo mitigate this restriction please use 'channelFlow' builder instead of 'flow'"
)
}
会检测emit所在的协程与collect所在的协程是否一致,不一致则抛出异常。
ChannelFlow
既然是安全问题,那就封装一个
kotlin
// 参数为SendChannel扩展函数
class MyFlow(private val block: suspend SendChannel<String>.() -> Unit) : Flow<String> {
// 构造Channel
private val channel = Channel<String>()
override suspend fun collect(collector: FlowCollector<String>) {
val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
coroutineScope.launch {
// 启动协程
// 模拟耗时,在子线程执行
Thread.sleep(3000)
// 把Channel对象传递出去
block(channel)
}
// 获取数据
val data = channel.receive()
// 发射
collector.emit(data)
}
}
重写了Flow的collect,当外界调用flow.collect时:
- 先启动一个协程
- 从channel里读取数据,没有数据则挂起当前协程
- 1里的协程执行,调用flow的闭包执行上游逻辑
- 拿到数据后进行发射,最终传递到collect的闭包
使用MyFlow:
kotlin
fun main() {
runBlocking {
val myFlow = MyFlow{
log("send")
send("send")
}
myFlow.collect{
log("collect")
}
}
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] send
[Thread[Test worker @coroutine#1,5,main]] collect
上下游不在同一个协程里执行,也不在同一个线程里执行。
ChannelFlow核心原理
上面FLow没有使用泛型,没有对Channel进行关闭,不完善。
kotlin
fun main() {
runBlocking {
val channelFlow = channelFlow {
log("send")
send("send")
}
channelFlow.collect{
log("collect:$it")
}
}
}
输出:
[Thread[Test worker @coroutine#2,5,main]] send
[Thread[Test worker @coroutine#1,5,main]] collect:send
分析原理:
kotlin
#ChannelFlow.kt
private open class ChannelFlowBuilder<T>(
//闭包对象
private val block: suspend ProducerScope<T>.() -> Unit,
context: CoroutineContext = EmptyCoroutineContext,
//Channel模式
capacity: Int = Channel.BUFFERED,
//Buffer满之后的处理方式,此处是挂起
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : ChannelFlow<T>(context, capacity, onBufferOverflow) {
//...
override suspend fun collectTo(scope: ProducerScope<T>) =
//调用闭包
block(scope)
//...
}
public abstract class ChannelFlow<T>(
// upstream context
@JvmField public val context: CoroutineContext,
// buffer capacity between upstream and downstream context
@JvmField public val capacity: Int,
// buffer overflow strategy
@JvmField public val onBufferOverflow: BufferOverflow
) : FusibleFlow<T> {
//produceImpl 开启的新协程会调用这
internal val collectToFun: suspend (ProducerScope<T>) -> Unit
get() = { collectTo(it) }
public open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
//创建Channel协程,返回Channel对象
scope.produce(context, produceCapacity, onBufferOverflow, start = CoroutineStart.ATOMIC, block = collectToFun)
//重写collect函数
override suspend fun collect(collector: FlowCollector<T>): Unit =
//开启协程
coroutineScope {
//发射数据
collector.emitAll(produceImpl(this))
}
}
produceImpl函数并不耗时,只是开启了新的协程。
kotlin
#Channels.kt
private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>, consume: Boolean) {
ensureActive()
var cause: Throwable? = null
try {
//循环从Channel读取数据
while (true) {
//从Channel获取数据
val result = run { channel.receiveCatching() }
if (result.isClosed) {
//如果Channel关闭了,也就是上游关闭了,则退出循环
result.exceptionOrNull()?.let { throw it }
break // returns normally when result.closeCause == null
}
//发射数据
emit(result.getOrThrow())
}
} catch (e: Throwable) {
cause = e
throw e
} finally {
//关闭Channel
if (consume) channel.cancelConsumed(cause)
}
}
ChannelFlow应用场景
如:buffer、flowOn、flatMapLatest、flatMapMerge等
callbackFlow 原理
collect所在的协程为runBlocking协程,而send函数虽然在新的协程里,但他的协程调度器使用的是collect协程的,send函数和collect函数运行的线程是同一个线程。 虽然可以更改外层的调度器使运行在不同的线程,但不够灵活:
kotlin
fun main() {
GlobalScope.launch {
val channelFlow = channelFlow {
log("send")
send("send")
}
channelFlow.collect{
log("collect:$it")
}
}
// 防止父线程过早退出
Thread.sleep(100)
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] send
[Thread[DefaultDispatcher-worker-2 @coroutine#1,5,main]] collect:send
kotlin
fun main() {
runBlocking {
val channelFlow = channelFlow {
getName(object :NetResult<String>{
override fun onSuc(t: String) {
log("begin")
trySend("trySend")
log("end")
}
override fun onFail(err: String) {}
})
}
channelFlow.collect{
log("下游收到:$it")
}
}
// 防止过早退出
Thread.sleep(2500)
}
fun getName(callback:NetResult<String>){
thread {
// 模拟网络耗时
Thread.sleep(2000)
callback.onSuc("finish")
}
}
interface NetResult<T>{
fun onSuc(t:T)
fun onFail(err:String)
}
输出:
[Thread[Thread-3,5,main]] begin
[Thread[Thread-3,5,main]] end
collect没有收到。getName函数内部开启了线程,它本身不是耗时操作,channelFlow闭包很快执行完成。CoroutineScope.produce的闭包执行结束后关闭channel。当子线程回调onSuc并执行trySend并不会往channel发送数据。
解决:不让协程关闭channel,只要协程没有结束,channel就不会关闭,在方法里调用挂起函数。
kotlin
fun main() {
runBlocking {
val channelFlow = channelFlow {
getName(object :NetResult<String>{
override fun onSuc(t: String) {
log("begin")
trySend("trySend")
log("end")
// 关闭channel,触发awaitClose闭包执行
close()
}
override fun onFail(err: String) {}
})
awaitClose{
// 走到此,channel关闭
log("awaitClose")
}
}
channelFlow.collect{
log("下游收到:$it")
}
}
// 防止过早退出
Thread.sleep(2500)
}
输出:
[Thread[Thread-3,5,main]] begin
[Thread[Thread-3,5,main]] end
[Thread[Test worker @coroutine#1,5,main]] 下游收到:trySend
[Thread[Test worker @coroutine#2,5,main]] awaitClose
- awaitClose挂起协程,该协程不结束,则channel不关闭
- channel使用完成后需要释放资源,主动调用channel的close函数,该函数最终会触发awaitClose闭包执行,在闭包里做一些释放资源的操作。
callbackFlow
kotlin
fun main() {
runBlocking {
val callbackFlow = callbackFlow {
getName(object :NetResult<String>{
override fun onSuc(t: String) {
log("begin")
trySend("trySend")
log("end")
}
override fun onFail(err: String) {}
})
awaitClose{
// 走到此,channel关闭
log("awaitClose")
}
}
callbackFlow.collect{
log("下游收到:$it")
}
}
}
输出:
[Thread[Thread-3,5,main]] begin
[Thread[Thread-3,5,main]] end
[Thread[Test worker @coroutine#1,5,main]] 下游收到:trySend
callbackFlow可以很好的使用。
Flow与Channel互转
Flow和Channel可以借助ChannelFlow互转。
kotlin
fun main() {
runBlocking {
val channel = Channel<String> { }
val flow = channel.receiveAsFlow()
GlobalScope.launch {
flow.collect{
log("collect:$it")
}
}
delay(200)
channel.send("send")
}
// 防止过早退出
Thread.sleep(250)
}
channel通过send,flow通过collect收集
Flow 转 Channel
kotlin
fun main() {
runBlocking {
val flow = flow {
emit("emit")
}
val channel = flow.produceIn(this)
val data = channel.receive()
log("receive:$data")
}
}
flow.produceIn(this)触发collect操作,进而执行flow闭包,emit将数据放到channel里,最后通过channel.receive取数据