Coroutine之Channel与Flow的相互转换


源码中对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窗口内。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值