Android协程值变更同步:ViewModel表单校验场景如何避免竞态问题?
问题分析
你的现有实现存在两个核心问题:
- 协程执行逻辑错误:单个协程内的
collect是挂起函数,会一直等待当前流结束才会执行后续代码,所以你写的getAge().collect永远不会被执行,目前能跑通大概率是你的getName()返回的是仅发射一次就结束的冷流 - 共享可变变量非原子操作风险:如果后续你把两个
collect放到不同协程实现并行收集,++valid属于读改写三步操作,没有同步保护的情况下并发执行确实会出现计数丢失,导致校验通过也无法触发跳转
最优解决方案
推荐直接使用Kotlin Flow的combine操作符,不需要自行维护校验计数,天然避免并发问题,代码更简洁高效:
fun onContinueClick() { viewModelScope.launch { // 合并姓名、年龄两个流,任意一个流更新时都会用两个流的最新值做校验 combine( getName(), getAge() ) { name, age -> name != null && age != null } .collect { isAllValid -> if (!isAllValid) return@collect // 两项都不为空校验通过,执行跳转逻辑 nextPageUseCase().collect { _navigationNotify.value = it } } } }
自行维护计数的同步方案
如果业务需要必须自己维护校验计数,可以用以下两种方式做同步:
- 使用原子类
AtomicInteger:计数增减都是原子操作,无锁性能高,适合简单计数场景fun onContinueClick() { val navigateNextPage: (Int) -> Unit = lambda@{ validation -> if (validation < 2) return@lambda nextPageUseCase().onEach { _navigationNotify.value = it }.launchIn(viewModelScope) } viewModelScope.launch { val valid = AtomicInteger(0) // 每个流单独开协程收集,避免串行阻塞 launch { getName().collect { name -> if (name != null) navigateNextPage(valid.incrementAndGet()) } } launch { getAge().collect { age -> if (age != null) navigateNextPage(valid.incrementAndGet()) } } } } - 使用
Mutex互斥锁:适合更复杂的同步逻辑,修改共享变量前加锁,修改完成后释放锁即可
内容的提问来源于stack exchange,提问作者Nishant Jalan
相关产品推荐
相关产品推荐

