Android 在单个作业中执行两个不同的操作

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.

我试图在一个作业中使用Room执行两个数据库操作,并且只执行第一个操作

在我的ViewModel中,我有以下内容:

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开发者博客-不久前有一篇关于此的帖子。或者,选中此选项—“带有
返回类型的查询始终在文件室执行器上运行,因此它们始终是主安全的。您不需要在代码中执行任何操作以使它们脱离主线程。”