进行多个协程 API 调用并等待所有调用

Posted

技术标签:

【中文标题】进行多个协程 API 调用并等待所有调用【英文标题】:Making multiple coroutine API calls and waiting all of them 【发布时间】: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 并等待主题会有所帮助 【参考方案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 调用成功),这意味着该响应稍后出现。这不起作用。

以上是关于进行多个协程 API 调用并等待所有调用的主要内容,如果未能解决你的问题,请参考以下文章

高性能编程之协程--------asyncio

异步IO和协程

在 iOS 中等待多个网络异步调用

从使用协程进行网络调用的服务内部发送广播

在执行下一个代码块之前等待多个 RxJS 调用

线程池+协程+gevent模块