源码中对ChannelFlow的定义为:ChannelFlow是一个Flow,但是使用了Channel对它进行扩展,并且二者始终彼此融合在一起。ChannelFlow是一个虚类,其继承关系图为:
ChannelFlow主要是用于Channel与Flow相互转换的,下面通过不同的转换方式来看其原理:
1. Channel转化成Flow
1.1. 单播式
定义了两个ReceiveChannel的扩展函数consumeAsFlow()/receiveAsFlow()来将Channel转化成Flow。下面看一下其基本使用:
private fun asFlowExample() {
val subject = Channel<Int>()
val channelFlow = subject.receiveAsFlow()
runBlocking {
launch {
channelFlow.collect {
println("subject one: $it")
}
}
repeat(2) {
subject.send(it)
}
//注意只有Channel关闭了runBlocking协程才结束
subject.close()
}
}
下面看一下这断代码的执行流程。先是创建了一个Channel对象,然后将该Channel对象转化为Flow对象channelFlow:
public fun <T> ReceiveChannel<T>.receiveAsFlow(): Flow<T> =
ChannelAsFlow(this, consume = false)
根据注释该扩展函数是将Channel转化程一个热流,与consumeAsChannel()不同的是consume参数传值不同,其结果是consumeAsChannel()转化后的流只能被collect一次。可以看到receiveAsFlow()只是将Channel对象做为参数初始化了一个ChannelAsFlow对象。这段代码中有两个协程,父协程是生产者协程,子协程是消费者协程。接下来先执行生产者协程,这一过程在Kotlin协程Channel中receive与send原理分析中分析过了。然后执行消费者协程,Flow的collect()方法我们很熟悉:
public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
接着会调用Flow的成员方法collect(),而此时的Flow的实际类型是ChannelAsFlow,因此会调用其collect()方法:
//ChannelAsFlow.kt
override suspend fun collect(collector: FlowCollector<T>) {
if (capacity == Channel.OPTIONAL_CHANNEL) {
markConsumed()
collector.emitAllImpl(channel, consume) // direct
} else {
super.collect(collector) // extra buffering channel, produceImpl will mark it as consumed
}
}
此时capacity是ChannelAsFlow构造函数传入的默认参数,为OPTIONAL_CHANNEL,因此会执行markConsumed()方法,该方法是将consumed成员变量设置为构造函数传进来的参数consume的值,若consumed的值已经为true则会抛出异常:
//class ChannelAsFlow
private fun markConsumed() {
if (consume) {
check(!consumed.getAndSet(true)) { "ReceiveChannel.consumeAsFlow can be collected just once" }
}
}
接着执行FlowCollector的扩展方法emitAllImpl():
private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>, consume: Boolean) {
var cause: Throwable? = null
try {
while (true) {
//这里的run是为了实现同步,保证多次collect时每次发射的result不重复,且接收也不重复
val result = run { channel.receiveOrClosed() }
//如果接收到的结果是一个Closed对象,则结束循环
if (result.isClosed) {
result.closeCause?.let { throw it }
break // returns normally when result.closeCause == null
}
emit(result.value)
}
} catch (e: Throwable) {
cause = e
throw e
} finally {
if (consume) channel.cancelConsumed(cause)
}
}
这段代码主要是执行一个循环,先是从channel中接收一个元素,这一过程参考Kotlin协程Channel中receive与send原理分析。然后判断该reult类型是否为Closed,若是则跳出循环,从这里可知Channel的close操作实际是向队列中加入一个Closed对象。接着是调用FlowCollector对象的emit()方法,这一过程参考Kotlin中flow发射与接收分析。最后若consume为true,则将取消掉。
1.2. 广播式
对于单播Chanel转换成Flow后,假如存在多个collect的消费者,当向Chanel中发送一个元素后,每个消费者不一定都会收到该元素,收到该元素的消费者是不确定的;若要让每个消费者都收到该元素,着就需要一个广播式的Chanel:BroadcastChanel,其转换为Flow的函数为:
/**
* Represents the given broadcast channel as a hot flow.
* Every flow collector will trigger a new broadcast channel subscription.
*
* ### Cancellation semantics
* 1) Flow consumer is cancelled when the original channel is cancelled.
* 2) Flow consumer completes normally when the original channel completes (~is closed) normally.
* 3) If the flow consumer fails with an exception, subscription is cancelled.
*/
@FlowPreview
public fun <T> BroadcastChannel<T>.asFlow(): Flow<T> = flow {
emitAll(openSubscription())
}
从注释可以看出:每一个接收者会触发一个新的广播Chanel订阅。要弄清楚这个过程,先看一个flow函数的定义:
@PublishedApi
internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
return object : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
collector.block()
}
}
}
flow()函数实例化了一个Flow对象,当该Flow对象被collect时,Flow对象的collect()函数被调用,则block代码块会被执行,即是上一段代码的emitAll(openSubscription())会执行。emitAll()函数是一个suspend函数,里面运行一个while循环,不断从chanel中去取元素:
private suspend fun <T> FlowCollector<T>.emitAllImpl(channel: ReceiveChannel<T>, consume: Boolean) {
var cause: Throwable? = null
try {
while (true) {
val result = run { channel.receiveOrClosed() }
if (result.isClosed) {
result.closeCause?.let { throw it }
break // returns normally when result.closeCause == null
}
emit(result.value)
}
} catch (e: Throwable) {
cause = e
throw e
} finally {
if (consume) channel.cancelConsumed(cause)
}
}
上文说**“每一个接收者会触发一个新的广播Chanel订阅”**,那么新的订阅在哪里实现的呢?答案就在openSubscription()中:
//ArrayBroadcastChanel.kt
public override fun openSubscription(): ReceiveChannel<E> =
Subscriber(this).also {
updateHead(addSub = it)
}
这里新创建了一个Subscriber实力,并通过updateHead()函数将其添加到subscribers链表中,具体逻辑参看下一节对BroadcastChanel的原理分析。这样对于转化后的Flow的每一次collect都会向该BroadcastCanel中注册一个subscriber,因此当Chanel中有数据到来时,每一个collect代码块中均会收到该数据。
2. Flow转换程Channel
2.1. produceIn()转换方式
produceIn()转换创建了一个produce协程来collect原Flow
,因此该produce协程应该在恰当时候被关闭或者取消。转换后的Channel拥有处理背压的能力。其基本使用方式如下:
runBlocking {
val flow = flow<Int> {
repeat(10) {
delay(1000)
emit(it)
}
}
val produceIn = flow.produceIn(this)
repeat(10) {
delay(100)
val receive = produceIn.receive()
println("receive: $receive")
}
}
}
该代码片先创建一个Flow,创建的源码如下:
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
val safeCollector = SafeCollector(collector, coroutineContext)
try {
safeCollector.block()
} finally {
safeCollector.releaseIntercepted()
}
}
}
这段代码之前分析过,主要是创建一个SafeFlow对象。接着示例代码调用Flow的扩展函数produceIn():
public fun <T> Flow<T>.produceIn(
scope: CoroutineScope
): ReceiveChannel<T> =
asChannelFlow().produceImpl(scope)
先调用asChannelFlow()函数将Flow转换成ChanelFlow:
internal fun <T> Flow<T>.asChannelFlow(): ChannelFlow<T> =
this as? ChannelFlow ?: ChannelFlowOperatorImpl(this)
这里的this是一个SafeFlow对象,因此会将this做为参数创建一个ChannelFlowOperatorImpl对象。然后调用ChannelFlowOperatorImpl的父类ChannelFlow的produceImpl()方法:
//class ChannelFlow
open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
scope.produce(context, produceCapacity, start = CoroutineStart.ATOMIC, block = collectToFun)
produceImpl()方法中会创建一个produce协程:
public fun <E> CoroutineScope.produce(
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = 0,
start: CoroutineStart = CoroutineStart.DEFAULT,
onCompletion: CompletionHandler? = null,
@BuilderInference block: suspend ProducerScope<E>.() -> Unit
): ReceiveChannel<E> {
//注意,这里创建的Channel即是转换成的Channel对象
val channel = Channel<E>(capacity)
val newContext = newCoroutineContext(context)
val coroutine = ProducerCoroutine(newContext, channel)
if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
coroutine.start(start, coroutine, block)
return coroutine
}
这里新创建一个Channel,这里的capacity=BUFFERED, 因此会创建一个ArrayChannel对象。将该ArrayChannel做为参数构造一个ProducerCoroutine实例,ProducerCoroutine继承于ChannelCoroutine,ChannelCoroutine的签名为:
internal open class ChannelCoroutine<E>(
parentContext: CoroutineContext,
protected val _channel: Channel<E>,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active), Channel<E> by _channel
可以看出ChannelCoroutine既是一个协程又是一个Channel。接下来会调用该协程的start()方法,在Coroutine挂起与恢复分析中分析了协程的开启过程,最终会执行以下代码:
public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) = runSafely(completion) {
createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
//IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(receiver, probeCompletion)
else {
//注意这里的this即是block: suspend ProducerScope<E>.() -> Unit
createCoroutineFromSuspendFunction(probeCompletion) {
//将没有参数的lambda表达式转换成两个参数的lambda表达式,receiver即外面传入的,it从后文可知是一个Continuation的子类对象
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
//IntrinsicsJvm.kt
private inline fun <T> createCoroutineFromSuspendFunction(
completion: Continuation<T>,
//该lambda表达式有一个类型为Continuation的参数
crossinline block: (Continuation<T>) -> Any?
): Continuation<Unit> {
val context = completion.context
// label == 0 when coroutine is not started yet (initially) or label == 1 when it was
return if (context === EmptyCoroutineContext)
object : RestrictedContinuationImpl(completion as Continuation<Any?>) {
private var label = 0
override fun invokeSuspend(result: Result<Any?>): Any? =
when (label) {
0 -> {
label = 1
result.getOrThrow() // Rethrow exception if trying to start with exception (will be caught by BaseContinuationImpl.resumeWith
block(this) // run the block, may return or suspend
}
1 -> {
label = 2
result.getOrThrow() // this is the result if the block had suspended
}
else -> error("This coroutine had already completed")
}
}
else
object : ContinuationImpl(completion as Continuation<Any?>, context) {
private var label = 0
override fun invokeSuspend(result: Result<Any?>): Any? =
when (label) {
0 -> {
label = 1
result.getOrThrow() // Rethrow exception if trying to start with exception (will be caught by BaseContinuationImpl.resumeWith
block(this) // run the block, may return or suspend
}
1 -> {
label = 2
result.getOrThrow() // this is the result if the block had suspended
}
else -> error("This coroutine had already completed")
}
}
}
createCoroutineFromSuspendFunction()函数逻辑是根据context的类型来决定创建一个RestrictedContinuationImpl对象或是ContinuationImpl对象。最后执行Continuation的resumeCancellableWith()方法即协程开始执行。开启该协程后,block就会被执行,其经过转换后有两个参数,第一个参数即使传入的receiver参数,是创建的ProducerCoroutine对象,ProducerCoroutine实现了ProducerScope接口,该lambda表达式为:
//class ChannelFlow
internal val collectToFun: suspend (ProducerScope<T>) -> Unit
get() = { collectTo(it) }
//class ChannelFlowOperator
protected override suspend fun collectTo(scope: ProducerScope<T>) =
flowCollect(SendingCollector(scope))
//ChannelFlowOperatorImpl
override suspend fun flowCollect(collector: FlowCollector<T>) =
flow.collect(collector)
最终会调用到子类ChannelFlowOperatorImpl的flowCollect()方法,这里的collector是一个SendingCollector对象,flow是一个SafeFlow对象,这里的代码就比较熟悉了,在Kotlin中flow发射与接收分析分析过。这里的 flow.collect(collector)即会走到这一节开头的SafeFlow重写的collect(collector: FlowCollector)方法中。接下里就会执行flow {}中的代码了,因此会调用到SafeCollector的emit()方法,进而调用封装在SafeCollector内的emit()方法:
public class SendingCollector<T>(
private val channel: SendChannel<T>
) : FlowCollector<T> {
override suspend fun emit(value: T) = channel.send(value)
}
这里的channel即是之前创建的ProducerCoroutine对象。注意:这里的channel实际是collectTo(it)中的it,这里很奇怪因为collectToFun是有一个类型为ProducerScope的参数的,然而CoroutineScope.produce()方法的block却被定义为suspend ProducerScope.() -> Unit,没有参数,是通过 (this as Function2<R, Continuation, Any?>).invoke(receiver, it)代码实现的。到这里就完成了数据发射过程:flow {}中发射的数据即是向channel中发射一个数据,再从channel中取一个数据就可以完成数据的接收了。
2.2. broadcastIn转换方式
broadcastIn转换方式与produceIn转换方式实现原理一样,区别是创建来collect原Flow的协程不一样,broadcastIn转换方式创建的是一个broadcast协程。下面先看一下其基本用法:
runBlocking {
val flow = flow<Int> {
repeat(10) {
delay(1000)
emit(it)
}
}
val broadcastIn = flow.broadcastIn(this)
val receiveOne = broadcastIn.openSubscription()
val receiveTwo = broadcastIn.openSubscription()
repeat(10) {
val receive1 = receiveOne.receive()
val receive2 = receiveTwo.receive()
println("receive1: $receive1, receive2: $receive2")
}
}
因为创建方式与produceIn基本一样,因此省略详细过程,主要看不一样的点。先看一下是如何创建broadcast协程的:
public fun <E> CoroutineScope.broadcast(
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = 1,
start: CoroutineStart = CoroutineStart.LAZY,
onCompletion: CompletionHandler? = null,
@BuilderInference block: suspend ProducerScope<E>.() -> Unit
): BroadcastChannel<E> {
val newContext = newCoroutineContext(context)
val channel = BroadcastChannel<E>(capacity)
val coroutine = if (start.isLazy)
LazyBroadcastCoroutine(newContext, channel, block) else
BroadcastCoroutine(newContext, channel, active = true)
if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)
coroutine.start(start, coroutine, block)
return coroutine
}
这里capacity依然等于BUFFERED,因此会创建一个类型为ArrayBroadcastChannel的channel变量。start变量默认等于LAZY,因此会创建一个LazyBroadcastCoroutine,该协程继承于BroadcastCoroutine。示例代码中broadcastIn的实际类型是LazyBroadcastCoroutine,接下来不同的是需要往该LazyBroadcastCoroutine中添加订阅者:
//class LazyBroadcastCoroutine
override fun openSubscription(): ReceiveChannel<E> {
// 向ArrayBroadcastChannel中添加订阅者
val subscription = _channel.openSubscription()
// 协程已经开启过了
start()
return subscription
}
//class ArrayBroadcastChannel
public override fun openSubscription(): ReceiveChannel<E> =
Subscriber(this).also {
updateHead(addSub = it)
}
添加、删除及更新订阅者都是调用ArrayBroadcastChannel中的updateHead()方法,下面只贴出添加订阅者的代码:
private tailrec fun updateHead(addSub: Subscriber<E>? = null, removeSub: Subscriber<E>? = null) {
var send: Send? = null
bufferLock.withLock {
if (addSub != null) {
addSub.subHead = tail // start from last element
val wasEmpty = subscribers.isEmpty()
subscribers.add(addSub)
if (!wasEmpty) return // no need to update when adding second and etc sub
}
//省略
}
//省略
}
Subscriber对象被添加到链表subscribers的尾部,Subscriber中的subHead对应ArrayBroadcastChannel中缓存的某个元素的index。ArrayBroadcastChannel中,类型为Array成员变量buffer用来缓存发送到Channel中的数据,类型为CopyOnWriteArrayList的成员变量subscribers是用来保存注册到ArrayBroadcastChannel中的订阅者。Subscriber中的subHead表示要接收的发射到buffer中数据的index,每当Subscriber接收到buffer中的一个数据时,就会将其subHead值自增一,更新到buffer中下一个元素的位置。具体关系如下图所示:
下面看一下接收数据的实现过程,示例代码中的receiveOne和receiveTwo的类型是Subscriber,Subscriber继承于AbstractChannel,因此receiveOne.receive()方法:
//class AbstractChannel
public final override suspend fun receive(): E {
// fast path -- try poll non-blocking
val result = pollInternal()
@Suppress("UNCHECKED_CAST")
if (result !== POLL_FAILED && result !is Closed<*>) return result as E
return receiveSuspend(RECEIVE_THROWS_ON_CLOSE)
}
//class Subscriber
override fun pollInternal(): Any? {
var updated = false
val result = subLock.withLock {
val result = peekUnderLock()
when {
result is Closed<*> -> { /* just bail out of lock */ }
result === POLL_FAILED -> { /* just bail out of lock */ }
else -> {
// update subHead after retrieiving element from buffer
val subHead = this.subHead
this.subHead = subHead + 1
updated = true
}
}
result
}
// do close outside of lock
(result as? Closed<*>)?.also { close(cause = it.closeCause) }
// there could have been checkOffer attempt while we were holding lock
// now outside the lock recheck if anything else to offer
if (checkOffer())
updated = true
// and finally update broadcast's channel head if needed
if (updated)
broadcastChannel.updateHead()
return result
}
在pollInternal()方法中,如果向buffer中获取一个result数据成功即会更新subHead,并将result返回,即receive操作完成,成功拿到数据。在锁外面再重新检查是否有新数据添加进broadcastChannel里,不管成功向buffer中获取一个数据还是checkOffer()检查到subHead被更新了,都应该执行broadcastChannel.updateHead():
//class ArrayBroadcastChannel
private tailrec fun updateHead(addSub: Subscriber<E>? = null, removeSub: Subscriber<E>? = null) {
//省略
val minHead = computeMinHead()
val tail = this.tail
var head = this.head
val targetHead = minHead.coerceAtMost(tail)
//如果buffer窗口的起始位置大于subscribers窗口的起始位置,则不用更新
if (targetHead <= head) return // nothing to do -- head was already moved
var size = this.size
//需要将subscribers窗口的起始位置更新到大于buffer窗口的起始位置
while (head < targetHead) {
//清除窗口外的数据
buffer[(head % capacity).toInt()] = null
val wasFull = size >= capacity
// update the size before checking queue (no more senders can queue up)
this.head = ++head
this.size = --size
//不过buffer满了
if (wasFull) {
while (true) {
//取sender元素,并向buffer中填充数据
send = takeFirstSendOrPeekClosed() ?: break // when when no sender
if (send is Closed<*>) break // break when closed for send
val token = send!!.tryResumeSend(null)
if (token != null) {
assert { token === RESUME_TOKEN }
// 循环填充buffer
buffer[(tail % capacity).toInt()] = (send as Send).pollResult
this.size = size + 1
this.tail = tail + 1
return@withLock // go out of lock to wakeup this sender
}
}
}
}
return // done updating here -> return
}
// 省略
ArrayBroadcastChannel有两个窗口:buffer窗口和subscribers窗口。updateHead()另一个功能是保证subscribers窗口在buffer窗口内。