Kotlin协程runBlocking与stateIn使用异常:执行时长及输出不符预期
问题分析与解答
这不是Bug,是对stateIn函数的启动策略和协程作用域行为的理解有误,具体原因如下:
为什么会等待所有元素发射完成?
你在runBlocking作用域内调用stateIn(this)时,stateIn默认使用SharingStarted.Eagerly启动策略:
- 该策略会立即启动流的收集协程,只要传入的作用域(这里是
runBlocking的协程作用域)未被取消,就会持续收集流的全部元素。 - 而
runBlocking的核心特性是会等待其作用域内的所有子协程执行完毕才会退出,因此它会一直阻塞到(1..100)的所有元素都发射完成(耗时约100*100ms),才会完成numbers的初始化。
为什么输出的是最后一个元素?
StateFlow的本质是持续更新自身的value,同步接收流发射的每一个元素。在上述初始化过程中,它会依次接收1到100的所有值,最终value自然停留在最后一个元素100上。
满足预期的实现方式
如果你只想获取流的第一个元素就结束程序,不需要使用StateFlow,直接用first()操作符即可:
import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { println("hello") val firstValue = (1..100).asFlow() .onEach { delay(100) } .first() println(firstValue) println("bye") }
如果确实需要使用StateFlow,且希望它按需启动收集、避免阻塞初始化过程,可以使用SharingStarted.Lazily启动策略(只有当有订阅者时才开始收集),并在获取第一个值后停止收集:
import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking // 实际项目中建议使用与生命周期绑定的CoroutineScope,而非runBlocking的作用域 val numbers: StateFlow<Int> = (1..100).asFlow() .onEach { delay(100) } .stateIn( scope = runBlocking { this }, started = SharingStarted.Lazily, initialValue = 0 ) fun main() = runBlocking { println("hello") // 订阅并获取第一个值后取消收集 val collectJob = numbers.collect { value -> println(value) if (value == 1) collectJob.cancel() } collectJob.join() println("bye") }
注意:实际开发中不要用
runBlocking的作用域作为StateFlow的共享作用域,应该使用和组件生命周期绑定的协程作用域(如Android的viewModelScope),避免内存泄漏或不必要的协程运行。
内容的提问来源于stack exchange,提问作者AmirHossein Abdolmotallebi
相关产品推荐
相关产品推荐

