Kotlin协程与Flow执行差异疑问:不同Scope下输出为何不同?
Kotlin协程与Flow行为差异的原因
两段代码的输出差异核心在于协程的执行上下文、线程调度逻辑,以及StateFlow和first()的交互逻辑,具体拆解如下:
1. 协程的执行线程差异
- 第一段代码:
CoroutineScope(Dispatchers.Default).launch创建的协程,运行在后台线程池(Dispatchers.Default),和runBlocking所在的主线程是并行执行的关系。 - 第二段代码:直接调用的
launch继承了runBlocking的上下文,属于runBlocking的子协程,会在runBlocking所在的单线程串行执行(runBlocking默认使用当前线程的阻塞事件循环,所有子协程都要排队等待主线程空闲后才会执行)。
2. x.first()的执行逻辑差异
MutableStateFlow会保存当前最新值,调用first()(Flow的终端挂起函数)时的行为在两种场景下完全不同:
第二段代码场景:
runBlocking主线程先执行println("parent scope"),紧接着调用x.first()。- 此时
x的初始值是null,first()会立即收集到这个初始值并返回,所以先打印null。 - 主线程处理完
first()后,才会去执行队列里的子协程,因此后续才输出starting of this scope和ending of this scope。
第一段代码场景:
runBlocking主线程执行println("parent scope")后调用x.first(),此时主线程挂起,等待Flow发射新值。- 后台线程池的协程并行启动,先打印
starting of this scope,随后调用x.emit(3)——这会更新x的最新值为3,并触发发射。 - 主线程的
first()收到发射的3后恢复执行,打印3。而后台线程的协程在emit(3)后继续执行println("ending of this scope"),由于线程调度的随机性,这段输出可能出现在3之前(就像用户提供的结果那样)。
3. 协程父子关系的间接影响
- 第二段的
launch是runBlocking的子协程,runBlocking会等待所有子协程完成,但这只决定runBlocking本身的结束时机,不影响x.first()的执行顺序——子协程必须等主线程当前的代码(println("parent scope")和x.first())执行完才会启动。 - 第一段的
launch不属于runBlocking的子协程,runBlocking不会主动等待它,但因为x.first()挂起等待值的发射,最终还是会等到后台协程的emit(3)执行后才继续。
内容的提问来源于stack exchange,提问作者Akshat Singhal
相关产品推荐
相关产品推荐

