使用协程的并行请求

Parallel requests with coroutines

我正在尝试从多个位置获取一些数据来填充 recyclerView。我以前使用回调,效果很好,但需要重构为协程。

所以我有一个改造服务列表,并并行调用它们中的每一个。然后我可以使用 onResponse 回调更新 recyclerView。我怎样才能用协程实现这个。

我试过类似的方法,但是下一个调用在我得到响应后被触发:

runblocking {
    for (service in services) {
        val response = async(Dispatchers.IO) {
            service.getResponseAsync()
        }
        adapter.updateRecyclerView(response.await())
    }
}

使用另一种方法时,我遇到了无法返回主线程更新我的 ui 的问题,因为我正在使用启动并且无法等待响应:

runblocking {
    services.foreach {
        launch(Dispatcher.IO) {
            val response = it.getResponseAsync()
        }
        withContext(Dispatcher.Main) {
            adapter.updateRecyclerView(response)
        }
    }
}

我很感谢你给我的每一个提示 ;) 干杯帕特里克

使用 launch 而不是 runBlocking 启动协程。下面的示例假设您从默认使用 Dispatchers.Main 的上下文启动。如果不是这种情况,您可以对这些使用 launch(Dispatchers.Main)

如果您想在每次任何并行操作 returns 时更新您的视图,请将您的 UI 更新移动到您为每个 [=16= 启动的协程中] 项:

for (service in services) {
    launch {
        val response = withContext(Dispatchers.IO) { service.getResponseAsync() }
        adapter.updateRecyclerView(response)
    }
}

如果您只需要在它们全部返回后更新,您可以使用awaitAll。在这里,您的 updateRecyclerView 函数必须编写为处理一系列响应,而不是一次处理一个响应。

launch {
    val responses = services.map { service ->
        async(Dispatchers.IO) { service.getResponseAsync() }
    }
    adapter.updateRecyclerView(responses.awaitAll())
}

await() 调用挂起当前协程并释放当前线程以供其他排队的协程附加。

因此,当调用 await() 时,当前协程会挂起,直到收到响应,这就是 for 循环未完成的原因(在 before 请求完成之前进入下一次迭代)。


首先,您不应该在这里使用 runBlocking,强烈建议不要在生产环境中使用它。

您应该改为使用 android 提供的 ViewModel 范围进行结构化并发(如果不再需要则取消请求,例如 activity 的生命周期结束)。

您可以在 activity 或片段 viewModelOwner.viewModelScope.launch(/*Other dispatcher if needed*/) {} 中使用像这样的视图模型范围,或者自己制作一个协程范围,并附加一个在 onDestroy 时自行取消的作业。


针对协程不并行请求的问题,可以在for循环中不await(ing)发起多个请求。

和select他们,使用select表达式https://kotlinlang.org/docs/reference/coroutines/select-expression.html#selecting-deferred-values

示例:

viewModelOwner.viewModelScope.launch {
    val responses = mutableListOf<Deferred<TypeReturnedFromGetResponse>>()
    for (service in services) {
        async(Dispatchers.IO) {
            service.getResponseAsync()
        }.let(responses::add)
    }

    // adds which ever request is done first in oppose to awaiting for all then update
    for (i in responses.indices) {
        select<Unit> {
            for (response in responses) {
                response.onAwait {
                    adapter.updateRecyclerView(it)
                }
            }
        }
    }
}

PS:使用此方法看起来很丑陋,但会在首先解决任何请求后立即更新适配器,而不是等待每个请求然后更新其中的项目.