【问题标题】:It is safe to request dao flow from UI thread从 UI 线程请求 dao 流是安全的
【发布时间】:2021-08-18 19:21:10
【问题描述】:

如果我们用@Query("..") fun itemList(): List<Item> 声明一个数据库操作,我们必须确保代码在后台线程上运行。

如果我们将其声明为挂起函数@Query("..") suspend fun itemList(): List<Item>,则room 生成的代码会创建一个协程并通过CoroutinesRoom.execute 发布它——在主线程上调用是安全的

但是使用 Flow 返回类型声明的操作呢? @Query("..")fun itemList(): Flow<List<Item>> 在主线程上调用它们安全吗?

我的看法是,查看 room 生成的源代码,从 UI 线程调用该函数并不是很糟糕。有一些同步代码在调用线程上运行 - 但 db 操作是在后台线程上完成的。

【问题讨论】:

  • 你找到解决办法了吗?
  • 之前你说过请求流不会阻塞主线程。但是,在请求流时运行的一些同步代码可能会阻塞主线程(仅在请求流时,而不是在流运行时)。
  • 然而,我们发现影响几乎不存在
  • 我添加了一些额外的解释,希望我的回答对您有所帮助
  • 有趣的是,当应用创建流对象时,哪些代码会阻塞主线程? room 不会为此触及任何同步部分。 (在主线程上运行的任何代码都会阻塞主线程)

标签: android android-room flow suspend


【解决方案1】:

对于您的情况,@Query("..")fun itemList(): Flow<List<Item>> room 生成流 - 这就像可观察模式,您应该订阅流以接收发出的值。您应该在流上调用 collect 函数,该函数应该从协程范围和 will not blocks main thread 调用(就像在您的示例中使用暂停 @Query 一样)。这就是为什么你不应该考虑线程只是选择正确的范围。 Please see sample for android.

用户调用时只会创建冷流对象:

@Query("..")fun observeItemList(): Flow<List<Item>>

在调用该方法的线程上。要创建流对象room 生成RoomSQLiteQuery 对象:只需从@Query("..") 构建语句的字符串表示并绑定查询参数(如果它们存在)(应用房间的类型转换器)。然后room调用CoroutinesRoom.createFlow()方法创建流对象by flow builder。 flow builder(bock) 的所有代码都将在调用 collect 方法的in context of coroutine 处执行。

我写了一个小例子:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow

fun main() {
    println("main thread: ${Thread.currentThread()}") // main thread.
    runBlocking {
        println("runBlocking thread: ${Thread.currentThread()}") // main thread too.
        launch(Dispatchers.Default) {
            println("launch thread create flow: ${Thread.currentThread()}")// DefaultDispatcher thread
            val myFlow = observeItemList()
            launch (newSingleThreadContext("MyOwnThread")){ // declare context of the coroutine
                println("launch collect thread: ${Thread.currentThread()}")// MyOwnThread thread
                myFlow.collect {
                    println("flow collect thread: ${Thread.currentThread()}") // in the context of the calling coroutine (MyOwnThread thread)
                    println("emited value: $it")
                }
            }
        }
        println("some code in main.")
    }
}

fun observeItemList() : Flow<Int> {
    println("call to observeItemList thread: ${Thread.currentThread()}") // DefaultDispatcher thread
    return flow{
        println("flow builder thread: ${Thread.currentThread()}") // MyOwnThread thread
        repeat(3) {
            emit(it)
            delay(1000)
        }
    }
}

输出:

main thread: Thread[main,5,main]
runBlocking thread: Thread[main,5,main]
some code in main.
launch thread create flow: Thread[DefaultDispatcher-worker-1,5,main]
call to observeItemList thread: Thread[DefaultDispatcher-worker-1,5,main]
launch collect thread: Thread[MyOwnThread,5,main]
flow builder thread: Thread[MyOwnThread,5,main]
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 0
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 1
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 2

这就是为什么所有用于创建查询语句、参数绑定、创建流对象、按流收集发出的项目的代码都将在您调用的协程上下文(调度程序)的线程上执行:

launch(Right coroutine dispatcher){
// all code works on 'Right coroutine dispatcher'
    dao.observeItemList().collect{ }
}

在这种情况下,您只会在创建和启动协程时产生开销。

例如,如果您通过某种方法调用launchIn

fun test() {
     dao.observeItemList().forEach{}.launchIn(scope)
}

然后将在test() 调用线程上创建流,但流的构建器主体和收集将在scope 的调度程序上执行。 在这种情况下,您会在协程创建、启动和创建流对象(不执行流的构建器块)时产生开销。

如果你要在非主线程上运行什么,你应该付出任何代价——在主线程上做一些工作(我的意思是准备一些对象,在其他线程中启动任务)。

【讨论】:

    猜你喜欢
    • 2011-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-06
    相关资源
    最近更新 更多