如何用Kotlin Coroutine按顺序执行多函数?代码求评审
代码评审与修正建议
核心问题梳理
你的代码存在几个关键问题,包括协程与Firebase回调的兼容问题、并发安全风险、数组越界错误、字段赋值错误,以及执行逻辑重复等,下面逐个模块分析并给出修正方案:
1. 步骤1:userIdToArrayFun 异步回调未挂起
当前代码用Firebase的addOnSuccessListener回调处理异步请求,但withContext(Dispatchers.IO)会直接返回true,不会等待回调完成。这会导致后续步骤在userPointArray还未填充数据时就执行,完全偏离预期逻辑。
修正方案:使用Firebase的协程扩展(需引入kotlinx-coroutines-play-services依赖),将回调转换为挂起函数,确保函数等待请求完成后再返回:
// 先引入依赖:implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3" suspend fun userIdToArrayFun(): Boolean = withContext(Dispatchers.IO) { try { val documents = userDB.get().await() // 用await()替代回调,挂起直到请求完成 for (document in documents) { val userId = document.data?.getValue("userId").toString() val userEntry = UserPoint(userId, 0, 0) userPointArray.add(userEntry) } dataToUserPointArray.userIdToArray.value = true true // 成功返回true } catch (e: Exception) { e.printStackTrace() false // 失败返回false } }
2. 步骤2:calculatePointFun 并发安全与语义问题
- 四个子函数同时修改
userPointArray会有并发风险(ArrayList不是线程安全集合); - 返回值
false表示成功、true表示失败,语义反直觉,建议调整为true代表成功; - 子函数需确保是挂起函数,且修改共享集合时做线程同步。
修正方案:
suspend fun calculatePointFun(): Boolean = withContext(Dispatchers.IO) { try { // 用synchronized保证共享集合的线程安全 synchronized(userPointArray) { listOf( launch { stepCountToArrayFun() }, launch { diaryToArrayFun() }, launch { commentToArrayFun() }, launch { likeToArrayFun() } ).joinAll() } true // 成功返回true } catch (e: Throwable) { e.printStackTrace() false // 失败返回false } } // 示例子函数(需为挂起函数,修改集合时同步) suspend fun stepCountToArrayFun() = withContext(Dispatchers.IO) { synchronized(userPointArray) { // 这里写你的步数积分计算逻辑,比如根据userId匹配更新point userPointArray.forEach { user -> user.point += 10 // 模拟积分累加 } } }
3. 步骤3:indexArrayFun 数组越界与字段赋值错误
- 非挂起函数直接用
with(Dispatchers.IO)会报错,需改为suspend fun并使用withContext; - 循环中
i + 1会触发数组越界(最后一个元素时无下一个元素); - 错误地覆盖
point字段,实际应修改index字段。
修正方案:
suspend fun indexArrayFun(): Boolean = withContext(Dispatchers.IO) { try { // 按积分降序排序(如需升序则用compareBy) userPointArray.sortWith(compareByDescending { it.point }) var currentIndex = 1 for (i in userPointArray.indices) { userPointArray[i].index = currentIndex // 修正为修改index字段 // 仅当不是最后一个元素且下一个积分不同时,才递增排名 if (i != userPointArray.lastIndex && userPointArray[i + 1].point != userPointArray[i].point) { currentIndex++ } } true } catch (e: Exception) { e.printStackTrace() false } }
4. 执行代码:重复调用与顺序逻辑问题
当前代码调用了两次calculatePointFun(),导致四个子函数执行两次;同时原逻辑无法正确依赖上一步的执行结果。
修正方案:按顺序执行,确保上一步成功后再推进下一步:
// 优先使用已有协程作用域(如ViewModel的viewModelScope),避免手动创建导致泄漏 val fullUserArrayPointFun = CoroutineScope(Dispatchers.Default).launch { val step1Success = userIdToArrayFun() if (step1Success) { val step2Success = calculatePointFun() if (step2Success) { indexArrayFun() } } }
额外建议
- 避免全局
userPointArray,建议将其作为函数参数或封装在类内部,减少全局状态副作用; - 优先使用
mutableListOf<UserPoint>()而非ArrayList,更符合Kotlin集合使用习惯; - 协程作用域尽量复用框架提供的(如
viewModelScope、lifecycleScope),手动创建时需在合适时机取消,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Hyejung
相关产品推荐
相关产品推荐

