侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

使用协程并发执行多个网络请求并合并结果

2025-12-11 / 0 评论 / 4 阅读

题目

使用协程并发执行多个网络请求并合并结果

信息

  • 类型:问答
  • 难度:⭐⭐

考点

协程作用域管理, 结构化并发, 异常处理, 结果合并

快速回答

实现要点:

  • 使用coroutineScopeasync创建并发子任务
  • 通过try/catchCoroutineExceptionHandler处理异常
  • 使用awaitAll()等待所有请求完成
  • 合并结果时过滤失败请求

核心代码示例:

val results = coroutineScope {
    apiEndpoints.map { endpoint ->
        async {
            try {
                Result.Success(fetchData(endpoint))
            } catch (e: Exception) {
                Result.Failure(e)
            }
        }
    }.awaitAll()
}
## 解析

场景需求

在实际开发中,经常需要同时发起多个网络请求(如获取用户信息、订单列表、商品详情),并在所有请求完成后合并结果。要求:

  1. 并发执行多个独立请求
  2. 单个请求失败不影响其他请求
  3. 统一收集成功/失败结果
  4. 主线程安全更新UI

完整解决方案

// 1. 定义密封结果类
sealed class Result<T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure<T>(val error: Throwable) : Result<T>()
}

// 2. ViewModel中的实现
class MyViewModel : ViewModel() {
    private val apiService = RetrofitClient.apiService

    fun fetchMultipleData() = viewModelScope.launch {
        val results = coroutineScope {
            listOf("/users", "/orders", "/products").map { endpoint ->
                async {
                    try {
                        Result.Success(apiService.fetchData(endpoint))
                    } catch (e: Exception) {
                        Result.Failure<Data>(e)
                    }
                }
            }.awaitAll()
        }

        // 3. 处理结果
        val successes = results.filterIsInstance<Result.Success<Data>>()
        val failures = results.filterIsInstance<Result.Failure<Data>>()

        // 更新UI状态
        _uiState.value = UiState(
            data = successes.map { it.data },
            errors = failures.map { it.error.message ?: "Unknown error" }
        )
    }
}

核心原理

  • 结构化并发coroutineScope创建子作用域,确保所有子协程完成前不会退出
  • 并发控制async启动异步任务,awaitAll()等待所有结果
  • 异常隔离:单个请求的try/catch防止异常传播导致整体失败
  • 线程调度:viewModelScope自动在主线程更新UI

最佳实践

  1. 作用域选择:UI相关使用viewModelScope/lifecycleScope
  2. 错误处理:使用密封类封装结果,避免直接暴露异常
  3. 性能优化:限制并发数量(如使用Semaphore
  4. 取消传播:父协程取消时自动取消所有子协程

常见错误

错误示例问题分析修正方案
launch { 
  val job1 = async { ... }
  val job2 = async { ... }
}
未等待子协程完成,可能提前返回使用coroutineScopeawaitAll()
try {
  list.map { async { ... } }.awaitAll()
} catch (e: Exception) { ... }
一个失败导致整个块崩溃在每个async内部单独捕获异常
在async块中更新UI非主线程操作UI导致崩溃在launch(Dispatchers.Main)中更新

扩展知识

  • 结果流处理:使用flow { ... } + flatMapMerge实现背压控制
  • 高级异常处理supervisorScope配合CoroutineExceptionHandler
  • 测试方案:使用TestCoroutineDispatcher控制虚拟时间
  • 替代方案channelFlowFlow.combine处理动态请求流