【问题标题】:Making multiple coroutine API calls and waiting all of them进行多个协程 API 调用并等待所有调用
【发布时间】:2020-11-03 14:32:50
【问题描述】:

所以通常当您必须进行不同的 API 调用并等待时,您会执行以下操作:

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val apiResponse1 = api.get1() //suspend function
        val apiResponse2 = api.get2() //suspend function

        if (apiResponse1.isSuccessful() && apiResponse2.isSuccessful() { .. }
    }
}

但是如果我必须使用不同的参数进行多个并发的相同 API 调用会发生什么:

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()

        multipleIds.forEach { id ->
             val apiResponse1 = api.get1(id) //suspend function

             if (apiResponse1.isSuccessful()) {
                 content.find { it.id == id }.enable = true
             }
        }

        liveData.postValue(content)
    }
}

第二种方法的问题是它将遍历multipleIds列表的所有ID并进行异步调用,但content可能会在此之前发布。如何等待每个循环的所有响应完成,然后才能查看 postValue 的内容?

【问题讨论】:

  • 也许使用async 并等待主题会有所帮助

标签: android retrofit coroutine kotlin-coroutines suspend


【解决方案1】:

确保完成几个异步任务的首选方法是使用coroutineScope。它将暂停直到所有子作业,例如对launchasync 的所有调用均已完成。

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()
        
        coroutineScope {
            multipleIds.forEach { id ->
                launch { // this will allow us to run multiple tasks in parallel
                    val apiResponse = api.get(id)
                    if (apiResponse.isSuccessful()) {
                        content.find { it.id == id }.enable = true
                    }
                }
           }
        }  // coroutineScope block will wait here until all child tasks are completed
        
        liveData.postValue(content)
    }
}

如果您对这种相当隐含的方法感到不满意,您还可以使用更实用的方法,使用 async 将您的 id 映射到 Deferred 列表,然后等待它们。这也将允许您并行运行所有任务,但最终会得到按正确顺序排列的结果列表。

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()

        val runningTasks = multipleIds.map { id ->
                async { // this will allow us to run multiple tasks in parallel
                    val apiResponse = api.get(id)
                    id to apiResponse // associate id and response for later
                }
        }

        val responses = runningTasks.awaitAll()

        responses.forEach { (id, response) ->
            if (response.isSuccessful()) {
                content.find { it.id == id }.enable = true
            }
        }
      
        liveData.postValue(content)
    }
}

【讨论】:

    【解决方案2】:

    不要使用forEach,而是使用map,并在{} 块内执行相同操作。将映射结果保存到变量并发布此变量。

    【讨论】:

      【解决方案3】:

      为了获得并发行为,您需要为每个 id 启动一个新的协程。您可以将multipleIdscontent 移到withContext 块之外。您也可以在withContext 块之后发布结果,因为withContext 是一个暂停函数,因此在其中创建的每个协程都必须在发布结果之前完成。

      viewModelScope.launch {
          val multipleIds = listOf(1, 2, 3, 4, 5, ..)
          val content = arrayListOf<CustomObj>()
      
          withContext(dispatcherProvider.heavyTasks) {
              multipleIds.forEach { id ->
                  launch {
                      val apiResponse = api.get(id) //suspend function
                      if (apiResponse.isSuccessful()) {
                          content.find { it.id == id }?.enable = true
                      }
                  }
              }
          }
      
          liveData.value = content
      }
      

      【讨论】:

      • 当我在 liveData.value 处设置断点时 - 它总是按预期工作,所有列表项都是真的(api.get(id)- 已被调用并得到响应),但是当我运行时没有断点的代码,所有列表项都是错误的(日志显示 api 调用成功),这意味着该响应稍后出现。这不起作用。
      猜你喜欢
      • 2018-12-27
      • 2020-04-08
      • 1970-01-01
      • 2019-05-09
      • 1970-01-01
      • 2019-10-21
      • 1970-01-01
      • 1970-01-01
      • 2020-02-23
      相关资源
      最近更新 更多