lifecycleScope协程内条件不满足时viewModelScope函数仍被调用问题
问题:协程无视条件触发,突破逻辑边界?
我有一个感知生命周期的Fragment Scope协程函数,用来检查从父Activity接收的值是否符合要求。当值为null时,会调用一个包含viewModelScope.launch的函数,倒计时后弹出提示框告知用户该值会禁用部分功能。
但现在出现异常:即使if条件不成立,viewModelScope.launch所在的函数仍会被触发。我观察到if条件内的日志没打印,viewModelScope.launch外部的日志也没执行,但协程内部的代码却无视条件运行。
目前临时解决办法是把ViewModel中的该函数改成suspend函数并移除viewModelScope.launch,但我搞不懂为什么不满足条件的函数会被调用,协程难道能突破逻辑边界?
相关代码
Fragment 中的协程代码
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.status.collectLatest { binding.contentScanner.tvScannerStatus.text = it if (statusCheck(it) == null) { viewModel.reactToInactiveScanner(it) // 该函数在条件为false时仍被调用 } } } }
ViewModel 中的函数代码
fun reactToInactiveScanner(s: String) { viewModelScope.launch { for(i in 1..5) { if(isScannerUnavailable(s)) break delay(1000L) } _scannerActive.value = isScannerUnavailable(s) } }
问题原因分析
- 协程的遗留执行:
collectLatest会在新值到来时取消之前未完成的协程,但reactToInactiveScanner启动的是独立的viewModelScope协程,不受collectLatest的取消影响。哪怕后续if条件不满足,之前启动的协程还在后台运行,容易让你误以为是当前不满足条件的调用触发了代码。 - 日志的误导:如果函数内外部日志没执行,但协程代码跑了,大概率是之前某次满足条件时调用了该函数,启动的协程还没结束,后续执行被当成了当前不满足条件的触发。
- 判断逻辑的验证:先确认
statusCheck(it)的返回值是否存在偶发null情况,或者it的值在协程执行过程中是否发生变化。可以在if条件前后加日志,记录每次it的值和statusCheck(it)的结果,确认是否真的出现条件不满足但函数被调用的情况。
优化方案
除了改成suspend函数的方式,还有两种更稳妥的处理方式:
- 让协程受collectLatest控制:把
reactToInactiveScanner改成suspend函数,直接在collectLatest的协程里执行,新值到来时未完成的倒计时会被取消,不会出现遗留执行:suspend fun reactToInactiveScanner(s: String) { for(i in 1..5) { if(isScannerUnavailable(s)) break delay(1000L) } _scannerActive.value = isScannerUnavailable(s) } - 手动管理协程Job:在ViewModel里保存协程Job,每次调用前取消之前的Job,避免多个协程同时运行:
private var scannerJob: Job? = null fun reactToInactiveScanner(s: String) { scannerJob?.cancel() scannerJob = viewModelScope.launch { for(i in 1..5) { if(isScannerUnavailable(s)) break delay(1000L) } _scannerActive.value = isScannerUnavailable(s) } }
内容的提问来源于stack exchange,提问作者Hmerman6006
相关产品推荐
相关产品推荐

