You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 01:36:04