【问题标题】:Parallel requests with coroutines带有协程的并行请求
【发布时间】:2020-05-03 22:35:37
【问题描述】:

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

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

我尝试了类似的方法,但在我得到响应后触发了下一个呼叫:

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

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

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

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

【问题讨论】:

  • runBlocking 不应该在测试之外使用。它完全违背了使用协程的目的。出于好奇,您为什么选择使用它?我每天都在这里的问题中看到它,所以人们一定是从某个地方得到这个想法的。

标签: kotlin retrofit2 kotlin-coroutines


【解决方案1】:

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

【讨论】:

  • 确实如此。无论使用的异步原语(即 Promises、Tasks、Coroutines)或编程语言(即 JavaScript、Kotlin、C#)如何,这种情况的处理方式大致相同。开始。无论如何,很好的答案。
【解决方案2】:

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

所以当await() 被调用时,当前协程暂停直到收到响应,这就是为什么 for 循环没有完成(在完成之前的请求之前进入下一次迭代)。


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

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

您可以在活动或片段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)
                }
            }
        }
    }
}

PS:使用这种方法看起来很难看,但只要首先解决了任何请求,就会更新适配器,而不是等待每个请求然后更新其中的项目。

【讨论】:

  • 是的,很明显循环在请求完成之前没有完成。问题是我不知道如何以“协程”方式打破它。您的解决方案是我一直在寻找的,并且效果很好。我已经阅读了您之前链接的文档,但永远不会想出这个解决方案 :D :D 非常感谢。我真的很感激
猜你喜欢
  • 2020-02-27
  • 2020-01-21
  • 2022-01-01
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
相关资源
最近更新 更多