combineTranform原理

135 阅读2分钟

前言

本篇文章分析combineTransfrom原理,其核心与combine相同

介绍

combineTransfrom的作用是合并多个流,组成一个热流,在google推荐的MVI框架中常用

原理

CombineTransfrom

核心代码如下:

internal suspend fun <R, T> FlowCollector<R>.combineInternal(

    flows: Array<out Flow<T>>,

    arrayFactory: () -> Array<T?>?, // Array factory is required to workaround array typing on JVM

    transform: suspend FlowCollector<R>.(Array<T>) -> Unit

): Unit = flowScope { // flow scope so any cancellation within the source flow will cancel the whole scope

    val size = flows.size

    if (size == 0) return@flowScope // bail-out for empty input

    val latestValues = arrayOfNulls<Any?>(size)

    latestValues.fill(UNINITIALIZED) // Smaller bytecode & faster that Array(size) { UNINITIALIZED }

    // 使用channel实现

    val resultChannel = Channel<Update>(size)

    val nonClosed = LocalAtomicInt(size)

    var remainingAbsentValues = size

    for (i in 0 until size) {

        // Coroutine per flow that keeps track of its value and sends result to downstream

        launch {

            try {

                // 收集每个子流

                flows[i].collect { value ->

                    // 收集之后,就send给下面的两层while循环

                    resultChannel.send(Update(i, value))

                    // 害怕类似线程饥饿问题,即某个子流一致emit,导致其实流没有机会执行

                    yield() // Emulate fairness, giving each flow chance to emit

                }

            } finally {

                // Close the channel when there is no more flows

                if (nonClosed.decrementAndGet() == 0) {

                    resultChannel.close()

                }

            }

        }

    }



    /*

     * Batch-receive optimization: read updates in batches, but bail-out

     * as soon as we encountered two values from the same source

     */

    val lastReceivedEpoch = ByteArray(size)

    var currentEpoch: Byte = 0

    // 两层循环表示合并后的流是一个热流

    // 第一层循环用它的命名来描述就是一次纪元(Epoch),一次Epoch不能有相同的子流收到两次emit值

    while (true) {

        ++currentEpoch

        // Start batch

        // The very first receive in epoch should be suspending

        // 第一次wait channel send

        var element = resultChannel.receiveOrNull() ?: break // Channel is closed, nothing to do here

        // 该层while循环主要是check remainingAbsentValues

        while (true) {

            val index = element.index

            // Update values

            val previous = latestValues[index]

            latestValues[index] = element.value

            if (previous === UNINITIALIZED) --remainingAbsentValues

            // Check epoch

            // Received the second value from the same flow in the same epoch -- bail out

            // 如上所述这里如果再相同flow收到两次值,会使用第一次

            if (lastReceivedEpoch[index] == currentEpoch) break

            lastReceivedEpoch[index] = currentEpoch

            // wait channel send

            element = resultChannel.poll() ?: break

        }



        // Process batch result if there is enough data

        // 所有flow都有值了,就可以回调transform了

        if (remainingAbsentValues == 0) {

            /*

             * If arrayFactory returns null, then we can avoid array copy because

             * it's our own safe transformer that immediately deconstructs the array

             */

            val results = arrayFactory()

            if (results == null) {

                transform(latestValues as Array<T>)

            } else {

                (latestValues as Array<T?>).copyInto(results)

                transform(results as Array<T>)

            }

        }

    }

}

注意

通过代码分析可以看到,transform回调是处于第一层循环中的,如果transform内部有什么挂起函数和内部循环,下一次执行时很有可能失败的,所以有一个结论是:combineTransform {}里不能执行类似emitAll(热流)的操作,不然会阻塞。

错误做法

while() {

    while() {



    }



    while() {



    }

}

正确做法,应该将上述逻辑转为如下模型

while() {

    while() {



    }

}



while() {



}