与协程的并行请求

时间:2020-05-03 22:35:37

标签: kotlin retrofit2 kotlin-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)
        }
    }
}

每一个技巧我都很感谢;) 帕特里克欢呼

2 个答案:

答案 0 :(得分:4)

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

如果您希望每次并行操作返回时都更新视图,则将UI更新移至要为每个service项目启动的协程中:

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())
}

答案 1 :(得分:1)

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

因此,当调用await()时,当前协程将暂停直到接收到响应为止,这就是为什么for循环未完成(在请求完成之前转到下一个迭代)的原因。


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

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

您可以在活动或片段viewModelOwner.viewModelScope.launch(/*Other dispatcher if needed*/) {}中使用这样的视图模型范围,或者通过附加的作业自己创建协程范围,该作业会在onDestroy上自行取消。


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

然后使用选择表达式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:使用此方法看起来很丑陋,但是将在第一次解决请求后立即更新适配器,而不是等待每个请求然后更新其中的项目。