关于使用.flowOn(Dispatchers.Main)导致主线程阻塞且代码无限挂起的原因咨询
关于使用.flowOn(Dispatchers.Main)导致主线程阻塞且代码无限挂起的原因咨询
问题重现
先把你的代码贴出来方便梳理:
fun main() = runBlocking { val myFlow: Flow<String> = flow { emit("abc") }.flowOn(Dispatchers.Main) myFlow.collect { println(it) } }
你提到这段代码会无限挂起,但换成其他调度器或者去掉flowOn就正常,哪怕只发射一个值也出问题,这确实容易让人摸不着头脑,我来给你拆解下核心原因。
核心矛盾:主线程阻塞与调度器的依赖冲突
这里的问题本质是 runBlocking的阻塞特性 和 Dispatchers.Main的工作机制 撞在了一起:
runBlocking是在你调用它的当前线程(也就是主线程)启动协程的,而且它会直接占住主线程并阻塞,直到内部所有协程任务全部完成才会松手。Dispatchers.Main调度器的协程,必须依赖主线程的消息循环来执行任务(比如Android的Looper、桌面端的UI消息队列)。但此时主线程已经被runBlocking完全堵死了,消息循环根本没机会处理flowOn(Dispatchers.Main)分配的上游Flow执行任务。
简单说就是:
- 你用
runBlocking把主线程焊死了,不让它处理任何其他消息 flowOn(Dispatchers.Main)要求emit("abc")必须在主线程的消息循环里执行- 主线程被堵死,
emit的任务永远排不上队,下游的collect就一直等不到数据,自然无限挂起
为什么换调度器或去掉flowOn就正常?
- 去掉
flowOn时,上游的emit会直接在collect所在的协程上下文(也就是runBlocking的主线程上下文)执行,不需要依赖消息循环,直接就能发射数据,collect拿到后协程结束,runBlocking也跟着退出。 - 换成
Dispatchers.IO这类调度器时,上游的emit会在IO线程池执行,完全不占用主线程,哪怕主线程被runBlocking堵着,IO线程的任务依然能正常跑,数据能顺利传到下游,协程完成后runBlocking就退出了。
解决办法
如果你确实需要在主线程执行上游Flow逻辑,可以做这两个调整:
- 用结构化的协程作用域替代
runBlocking,比如自定义CoroutineScope(Dispatchers.Main)(Android里推荐用lifecycleScope,桌面端也可以用自定义作用域) - 必须用
runBlocking的话,给它指定一个非主线程的上下文,让主线程的消息循环能正常运转
举两个可行的调整示例:
// 方案1:用结构化协程作用域替代runBlocking fun main() { val mainScope = CoroutineScope(Dispatchers.Main) mainScope.launch { val myFlow: Flow<String> = flow { emit("abc") }.flowOn(Dispatchers.Main) myFlow.collect { println(it) } // 任务完成后可以取消作用域避免内存泄漏 mainScope.cancel() } // 桌面端可根据需要加线程等待,比如Thread.sleep(1000)让任务执行完 } // 方案2:给runBlocking指定非主线程上下文 fun main() = runBlocking(Dispatchers.IO) { val myFlow: Flow<String> = flow { emit("abc") }.flowOn(Dispatchers.Main) myFlow.collect { println(it) } }
本质上就是要给Dispatchers.Main的任务留出生存空间,别让runBlocking把主线程彻底堵死。




