Kotlin 如何等待流完成发送值

Kotlin 如何等待流完成发送值,kotlin,Kotlin,我的存储库中有一个函数“getUser”,它根据提供的id发出一个表示用户的对象 我需要在另一个类中使用这些值。我在ID列表上循环,并将收集的用户添加到新列表中。在调用return之前,如何在收集值时等待列表完成 private fun ComputeAttendesList(提醒:提醒):ArrayList{ val AttendesList=arrayListOf() for(提醒中的朋友。usersToShare){ repoScope.launch{ Repository.getUs

我的存储库中有一个函数“getUser”,它根据提供的id发出一个表示用户的对象

我需要在另一个类中使用这些值。我在ID列表上循环,并将收集的用户添加到新列表中。在调用return之前,如何在收集值时等待列表完成

private fun ComputeAttendesList(提醒:提醒):ArrayList{
val AttendesList=arrayListOf()
for(提醒中的朋友。usersToShare){
repoScope.launch{
Repository.getUser(friend.collect){
它?让{user->
如果(!AttendesList.contains(用户))
AttendesList.add(用户)
}
}
}
}
返回与会者列表
}

我不想使用实时数据,因为这不是与UI相关的类。

此代码中有多个问题需要解决:

  • getUser()
    用于返回单个
    用户
    ,但它当前返回一个
    它永远不会结束,也不会返回多个用户
  • 从多个并发查询构造用户列表的方式不是线程安全的(因为多个
    launch
    e在多线程IO dispatcher上执行,并且它们都直接更新相同的不安全列表)
  • 实际用例是从Firebase获取用户列表,但是使用了许多针对单个ID的查询,而不是单个查询
  • #1的解决方案 让我们先解决1。下面是一个版本的
    getUser()
    ,它为单个
    用户
    挂起,而不是返回

    suspend fun getUser(id:String):用户{
    val collectionReference=FirebaseFirestore.getInstance().collection(集合用户)
    val query=collectionReference.whereEqualTo(ID,ID)
    return query.get().await().let{it.toObjects(User::class.java)}.firstOrNull()
    }
    //请改用kotlinx协同程序播放服务库
    私有挂起有趣的任务。等待():T{
    返回SuspendCancelableCoroutine{cont->
    addOnCompleteListener{
    val e=异常
    如果(e==null){
    @抑制(“未选中的_CAST”)
    如果(已取消)继续取消()否则继续恢复(结果为T)
    }否则{
    续:除例外情况外的简历(e)
    }
    }
    }
    }
    
    事实证明,这个
    await()
    函数已经编写好了(以更好的方式),并且在中可用,因此您不需要亲自编写它

    #2的解决方案 如果我们不能按照#3重写整个过程,我们可以通过以下方式处理问题#2:

    private suspend fun computeAttendeesList(提醒:提醒):列表{
    返回提醒。usersToShare
    .map{friendId->
    repoScope.async{Repository.getUser(friendId)}
    }
    .map{it.await()}
    托利斯先生()
    }
    
    #3的解决方案 相反,我们可以直接向Firebase查询整个列表:

    suspend-fun-getUsers(id:List):List{
    val collectionReference=FirebaseFirestore.getInstance().collection(集合用户)
    val query=collectionReference。其中(ID,ID)
    return query.get().await().let{it.toObjects(User::class.java)}
    }
    
    然后以一种非常基本的方式消费它:

    private suspend fun computeAttendeesList(提醒:提醒):列表{
    return Repository.getUsers(remention.usersToShare)
    }
    
    或者,您可以使此函数阻塞(remove
    suspend
    ),并将调用包装到
    runBlocking
    (如果您确实需要阻塞当前线程)


    请注意,此解决方案没有强制执行任何调度程序,因此如果您需要特定的作用域或调度程序,可以使用
    withContext

    包装其中一个挂起函数调用。请不要发布屏幕截图,它们不可搜索,并且很难生成有用的响应,因为我们需要键入所有内容。请在您的问题中使用代码块粘贴代码。您希望您的
    ComputeAttendesList
    在等待时做什么?你想让它暂停吗?还是阻止当前线程?如果是前者,您应该使方法本身
    挂起
    ,如果是后者,您可以使用
    运行阻塞
    。在任何一种情况下,您都有多种方法来实现这一点,因此这将有助于获得更多关于您希望在哪个范围内运行东西,以及您希望阻止哪些线程(如果有)的信息。抱歉@Joffrey,我发布了代码。好的,我需要是一个阻塞代码,只有在计算了
    AttendesList
    之后,我才希望被返回以供另一个函数使用。另外,您的
    repoScope
    是单线程的吗?因为您同时更新的ArrayList在这里不是线程安全的。如果您希望它提供一个完成的列表,那么使用一个
    suspend fun getList():list
    并从挂起上下文调用它。我认为你试图用一种解决方案解决两个问题。流很好地发出了构建列表所需的值,但是如果您需要一个完成的列表,感兴趣的对象应该只接收这样的内容。是的,谢谢,#3是最好的方法,我刚刚更改了它,它可以工作。祝您有个美好的一天!
    fun getUser(id: String) = callbackFlow {
        val collectionReference: CollectionReference =
            FirebaseFirestore.getInstance().collection(COLLECTION_USERS)
    
        val query: Query = collectionReference.whereEqualTo(ID, id)
    
        query.get().addOnSuccessListener {
            val lst = it.toObjects(User::class.java)
            if (lst.isEmpty())
                offer(null)
            else
                offer(it.toObjects(User::class.java)[0])
        }
    
        awaitClose()
    }
    
    private fun computeAttendeesList(reminder: Reminder): ArrayList<User> {
        val attendeesList = arrayListOf<User>()
        for (friend in reminder.usersToShare) {
            repoScope.launch {
                Repository.getUser(friend).collect {
                    it?.let { user ->
                        if (!attendeesList.contains(user))
                            attendeesList.add(user)
                    }
                }
            }
        }
        return attendeesList
    }