前言
本篇文章分析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() {
}