Android 在单个作业中执行两个不同的操作
我试图在一个作业中使用Room执行两个数据库操作,并且只执行第一个操作 在我的ViewModel中,我有以下内容:Android 在单个作业中执行两个不同的操作,android,kotlin-coroutines,Android,Kotlin Coroutines,我试图在一个作业中使用Room执行两个数据库操作,并且只执行第一个操作 在我的ViewModel中,我有以下内容: val supervisorJob = SupervisorJob() val scope = CoroutineScope(Dispatchers.Main + supervisorJob) 我有两个数据库操作,返回我Flow,如下所示: 第一次手术: firstRepo.getSomeData1().collect { val size = it.
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Main + supervisorJob)
我有两个数据库操作,返回我Flow
,如下所示:
第一次手术:
firstRepo.getSomeData1().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data1 name: ${it.name}")
}
}
secondRepo.getSomeData2().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data2 name: ${it.name}")
}
}
第二次操作:
firstRepo.getSomeData1().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data1 name: ${it.name}")
}
}
secondRepo.getSomeData2().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data2 name: ${it.name}")
}
}
我试图以以下方式执行此操作,它只执行和打印firstRepo.getSomeData1
并忽略secondRepo.getSomeData2
我的方法:
fun getAllProtocols() = scope.launch {
withContext(Dispatchers.IO) {
firstRepo.getSomeData1().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data1 name: ${it.name}")
}
}
secondRepo.getSomeData2().collect {
it.forEach {
Log.d(TAG, "Data2 name: ${it.name}")
}
}
}
}
如果我将每个操作包装在launch{}
中,那么它们都会被执行
我无法理解为什么会发生这种情况?为什么它们不能在同一个工作中执行
还是强制一个作业只能执行一个异步或db操作?同一协同程序中的代码按顺序执行。 流也是连续的-
collect
函数只是一个常规的挂起函数,仅在收集整个流(或抛出错误)时返回
换句话说,查看代码,这意味着在第一个流完全收集之前,第二个流的收集无法开始
还有一个问题,第一个流永远不会被完全收集,因为它只是无限期地观察房间数据库的变化
您已经知道如何解决此问题-在单独的协同程序中启动收集操作以使其并发:
fun getAllProtocols() = scope.launch(Dispatchers.IO) {
launch {
firstRepo.getSomeData1().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data1 name: ${it.name}")
}
}
}
launch {
secondRepo.getSomeData2().collect {
it.forEach {
Log.d(TAG, "Data2 name: ${it.name}")
}
}
}
}
您的代码无法工作,因为协同程序中的代码是按顺序执行的
withContext
是一个阻塞函数,它在第一个流完成时等待代码完成。您可以使用以下内容:
scope.launch(Dispatchers.IO) {
supervisorScope {
launch {
firstRepo.getSomeData1().collect {
val size = it.size
Log.d(TAG, "Size of list is $size")
it.forEach {
Log.d(TAG, "Data1 name: ${it.name}")
}
}
}
launch {
secondRepo.getSomeData2().collect {
it.forEach {
Log.d(TAG, "Data2 name: ${it.name}")
}
}
}
}
}
supervisorScope
函数是一个阻塞函数,它等待子协同路由完成 @Guest21的代码不应该工作,因为这两个协同程序是在第一个协同程序的范围内启动的,当第一个协同程序完成时,子协同程序将死亡。你错了,代码工作。第一个协同程序不能只是“完成”——它必须等待它的两个子协同程序先完成。我很确定这里不需要Dispatchers.IO
。这些数据库操作不是可以挂起且不阻塞吗?您是对的,数据库操作不需要这些操作(房间有自己的IO调度器,它将切换到)。我之所以保留它,是因为OP在其原始代码中使用了它——如果这些列表预计很短且不经常更新,那么切换到IO dispatcher只是为了记录一些数据是没有意义的。但同样,这只是一个假设,我们不知道OP的用例有多重,更可能的是他们使用不当。这是我在这里看到的许多问题中最常见的错误。Room API最近似乎一直处于阻塞状态,然后切换到挂起状态,但文档中的示例没有更新。这里需要注意的另一件重要的事情是,IO dispatcher由多个线程支持,因此可能存在争用条件-OP,您的日志条目可能会交错。如果这是一个问题,那么你应该使用主调度程序或任何其他由单个线程支持的调度程序(或者如果你仍然想继续使用IO调度程序,则使用互斥)。查看android开发者博客-不久前有一篇关于此的帖子。或者,选中此选项—“带有流返回类型的查询始终在文件室执行器上运行,因此它们始终是主安全的。您不需要在代码中执行任何操作以使它们脱离主线程。”