题目
使用协程并发执行多个网络请求并合并结果
信息
- 类型:问答
- 难度:⭐⭐
考点
协程作用域管理, 结构化并发, 异常处理, 结果合并
快速回答
实现要点:
- 使用
coroutineScope或async创建并发子任务 - 通过
try/catch或CoroutineExceptionHandler处理异常 - 使用
awaitAll()等待所有请求完成 - 合并结果时过滤失败请求
核心代码示例:
val results = coroutineScope {
apiEndpoints.map { endpoint ->
async {
try {
Result.Success(fetchData(endpoint))
} catch (e: Exception) {
Result.Failure(e)
}
}
}.awaitAll()
}
## 解析
场景需求
在实际开发中,经常需要同时发起多个网络请求(如获取用户信息、订单列表、商品详情),并在所有请求完成后合并结果。要求:
- 并发执行多个独立请求
- 单个请求失败不影响其他请求
- 统一收集成功/失败结果
- 主线程安全更新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
最佳实践
- 作用域选择:UI相关使用
viewModelScope/lifecycleScope - 错误处理:使用密封类封装结果,避免直接暴露异常
- 性能优化:限制并发数量(如使用
Semaphore) - 取消传播:父协程取消时自动取消所有子协程
常见错误
| 错误示例 | 问题分析 | 修正方案 |
|---|---|---|
| 未等待子协程完成,可能提前返回 | 使用coroutineScope或awaitAll() |
| 一个失败导致整个块崩溃 | 在每个async内部单独捕获异常 |
| 在async块中更新UI | 非主线程操作UI导致崩溃 | 在launch(Dispatchers.Main)中更新 |
扩展知识
- 结果流处理:使用
flow { ... }+flatMapMerge实现背压控制 - 高级异常处理:
supervisorScope配合CoroutineExceptionHandler - 测试方案:使用
TestCoroutineDispatcher控制虚拟时间 - 替代方案:
channelFlow或Flow.combine处理动态请求流