使用协程的并行请求

tri*_*ice 6 kotlin retrofit2 kotlin-coroutines

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

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

我尝试过类似的事情,但在我得到回复后下一个电话被触发:

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

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

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

我很感激每一个提示;) 帕特里克干杯

Ten*_*r04 6

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

如果您想在每次并行操作返回时更新您的视图,请将您的 UI 更新移动到您为每个service项目启动的协程中:

for (service in services) {
    launch {
        val response = withContext(Dispatchers.IO) { service.getResponseAsync() }
        adapter.updateRecyclerView(response)
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您只需要在所有这些都返回后更新,则可以使用awaitAll. 在这里,您updateRecyclerView必须编写函数来处理响应列表,而不是一次一个。

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


Ani*_*ahu 2

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

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


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

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

您可以在 Activity 或 Fragment 中使用像这样的视图模型作用域,viewModelOwner.viewModelScope.launch(/*Other dispatcher if needed*/) {}或者自己创建一个协程作用域并附加一个作业,该作业会在 onDestroy 上自行取消。


对于协程不执行并行请求的问题,您可以在 for 循环内启动多个请求,而无需等待(ing)它们。

并使用选择表达式选择它们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)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

PS:使用这种方法看起来很难看,但一旦第一次解决请求就会更新适配器,而不是等待每个请求然后更新其中的项目。