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

Kotlin:如何在foreach循环中等待所有异步任务完成

解决方案

问题出在你直接调用launch启动协程后,没有等待这些协程全部完成就执行了后续代码。Kotlin协程提供了优雅的非阻塞方式等待所有子任务完成,下面是两种可行方案:

方案一:使用coroutineScope等待所有子协程

coroutineScope会自动挂起当前协程,直到内部所有子协程全部完成后才继续执行后续代码,完美适配你的需求:

val failedTasks = mutableListOf<ApiTask<T>>()
App.log("apiCallChained: startCall")
// 用coroutineScope包裹所有任务,等待全部完成
coroutineScope {
    apiBlocks.forEachIndexed { i, apiBlock ->
        App.log("apiCallChained: callingTask: $i")
        launch(Dispatchers.Main) {
            val (r, err) = withContext(Dispatchers.IO) {
                try {
                    apiBlock.apiBlock(this) to null
                } catch (e: ApiCallError) {
                    null to e
                }
            }

            when {
                r != null -> {
                    App.log("apiCallChained: callingTask: $i - success")
                    apiBlock.onSuccess(r)
                }
                err != null -> {
                    App.log("apiCallChained: callingTask: $i - error")
                    failedTasks.add(apiBlock)
                }
            }
        }
    }
}
// 所有任务完成后才会执行到这里
App.log("apiCallChained: endCall: ${failedTasks.size}")
if (failedTasks.isEmpty()) onSuccess.invoke() else onError.invoke(failedTasks)

方案二:使用async+awaitAll收集任务结果

如果需要更灵活地处理每个任务的返回值,可以用async创建Deferred对象,再用awaitAll等待全部完成:

val failedTasks = mutableListOf<ApiTask<T>>()
App.log("apiCallChained: startCall")
coroutineScope {
    // 收集所有异步任务
    val tasks = apiBlocks.mapIndexed { i, apiBlock ->
        async(Dispatchers.Main) {
            App.log("apiCallChained: callingTask: $i")
            val (r, err) = withContext(Dispatchers.IO) {
                try {
                    apiBlock.apiBlock(this) to null
                } catch (e: ApiCallError) {
                    null to e
                }
            }
            // 返回索引和结果对,方便后续处理
            Pair(i, apiBlock to err)
        }
    }
    // 等待所有任务完成,统一处理结果
    tasks.awaitAll().forEach { (i, pair) ->
        val (apiBlock, err) = pair
        if (err == null) {
            App.log("apiCallChained: callingTask: $i - success")
            // 这里可以提前在async中保存成功结果,避免重复调用apiBlock
            apiBlock.onSuccess(apiBlock.apiBlock(this))
        } else {
            App.log("apiCallChained: callingTask: $i - error")
            failedTasks.add(apiBlock)
        }
    }
}
App.log("apiCallChained: endCall: ${failedTasks.size}")
if (failedTasks.isEmpty()) onSuccess.invoke() else onError.invoke(failedTasks)

关键说明

  • 两种方案都依赖coroutineScope,它不会阻塞线程,而是挂起当前协程,直到所有子协程完成,是Kotlin协程中等待批量任务的标准方式。
  • 因为你在launch(Dispatchers.Main)中修改failedTasks,所有修改操作都在主线程执行,不存在线程安全问题;如果后续切换到其他线程修改,建议使用线程安全集合(如ConcurrentLinkedQueue)。

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:20:30