一、核心机制解析
-
默认上下文继承
Flow 的发射端(flow{}构建器)与收集端(collect)默认共享同一协程上下文。这意味着:- 若在
Dispatchers.Main中调用collect,则emit()操作默认也在主线程执行17 - 若在
Dispatchers.IO中收集流,则发射逻辑默认运行于 IO 线程18
kotlinCopy Code fun main() = runBlocking(Dispatchers.Main) { flow { println("发射线程: ${Thread.currentThread().name}") // 输出: 发射线程: main emit(1) }.collect { println("收集线程: ${Thread.currentThread().name}") // 输出: 收集线程: main } } - 若在
-
冷流特性与上下文绑定
Flow 作为冷流,每次collect触发流的重新执行,且始终继承调用collect时的协程上下文25。
二、上下文传递原理
-
发射与收集的协程关联
Flow 的collect函数会将当前协程的上下文传递给发射端,确保二者运行在同一上下文中37:kotlinCopy Code // FlowCollector 接口源码片段 interface FlowCollector<in T> { suspend fun emit(value: T) } // 发射逻辑通过调用 collect 时的协程上下文执行 -
**
flowOn操作符的上下文隔离**
使用flowOn可隔离上下游上下文:- 上游(
flowOn之前的操作):运行在指定调度器(如Dispatchers.IO) - 下游(
flowOn之后的操作):保持collect时的原始上下文17
flow { println("发射线程: ${Thread.currentThread().name}") // 输出: 发射线程: DefaultDispatcher-worker-1 emit(1) }.map { it * 2 } // 下游操作符 .flowOn(Dispatchers.IO) // 影响上游 .collect { println("收集线程: ${Thread.currentThread().name}") // 输出: 收集线程: main } - 上游(
三、注意事项
-
跨协程上下文限制
同一流的发射与收集必须在同一协程上下文中执行,否则会抛出IllegalStateException17:// ❌ 错误示例:不同协程中执行 val flow = flow { emit(1) } GlobalScope.launch(Dispatchers.IO) { flow.collect() } // 发射在 IO 线程 GlobalScope.launch(Dispatchers.Main) { flow.collect() } // 收集在主线程 → 抛出异常 -
背压与上下文切换
使用buffer()、conflate()等背压处理操作符时,需注意其可能引入的上下文切换(如内部使用 Channel 实现)。
四、案例
fun getFlow1() = flow{
kotlinx.coroutines.delay(6000)
println("上游发送 context:${Thread.currentThread().name}")
emit("A run")
}
fun getFlow2() = flow{
withContext(Dispatchers.IO){
kotlinx.coroutines.delay(6000)
println("上游发送 context:${Thread.currentThread().name}")
emit("A run")
}
}
fun getFlow3() = flow{
kotlinx.coroutines.delay(6000)
println("上游发送 context:${Thread.currentThread().name}")
emit("A run")
}.flowOn(Dispatchers.IO)
fun main():Unit = runBlocking{//顶级协程
getFlow1().collect{
println(it);println("下游发送 context:${Thread.currentThread().name}");
}
getFlow3().collect{
println(it);println("下游发送 context:${Thread.currentThread().name}");
}
getFlow2().collect{
println(it);println("下游发送 context:${Thread.currentThread().name}");
}
}
调用getFlow2()时报的异常错误信息,因为上游发送端与下游收集端上下文不同导致
五、总结对比
| 场景 | 发射端上下文 | 收集端上下文 | 关键操作 |
|---|---|---|---|
| 默认行为 | 继承 collect 的协程上下文 | 与发射端相同 | 无 |
使用 flowOn | 切换为 flowOn 指定调度器 | 保持不变 | flowOn(Dispatcher) |
| SharedFlow/StateFlow | 独立于收集者,上下文由发射者决定 | 由收集者所在协程决定 | 热流需手动管理上下文 |
通过上下文保存机制,Kotlin Flow 实现了生产-消费逻辑的上下文一致性,开发者可通过 flowOn 灵活控制异步操作,同时避免显式线程切换的复杂性