使用 async & await 从房间数据库获取数据

And*_*Dev 1 android deferred kotlin android-room kotlin-coroutines

我正在尝试使用async & awaitinside从 Room Database 获取数据,Coroutine Scope但在返回值时遇到问题。

这是我的代码:

fun getUserFromDB():Profile {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(Dispatchers.IO).launch {
        return profileDao.getUserProfile().await()
    }
}
Run Code Online (Sandbox Code Playgroud)

道:

@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile():Deferred<Profile>
Run Code Online (Sandbox Code Playgroud)

在这里我想从方法返回 userProfile 但我不能在范围内这样做,如果我从Coroutine scope.

注意:我不是在这里遵循 MVVM 模式,而是做一个简单的例子。

Dom*_*her 6

这里有一些错误,但我将首先解决主要问题。

fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(Dispatchers.IO).launch {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)

在 中getUserFromDB,从launch异步执行getUserFromDB并且在您返回之前不会完成。

为了保证完成,您有两个选择(如果您想使用协程)。

  1. 标记getUserFromDB为挂起函数。(强烈推荐)
suspend fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    val userProfile = withContext(Dispatchers.IO) {
        profileDao.getUserProfile().await()
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)
  1. 使用runBlocking.
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    val userProfile = runBlocking(Dispatchers.IO) {
        profileDao.getUserProfile().await()
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)

现在主要问题得到解决。这里有几件事违反了约定和指导方针。

  1. getUserProfile不应该返回Deferred<Profile>(特别是如果它已经挂起),因为这是一个不安全的原语,它应该只返回Profile。这样您就不会意外引入不需要的并发性,也不必编写await().
@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile

fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(Dispatchers.IO).launch {
        val userProfile = profileDao.getUserProfile() /* .await() */
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)
  1. 在 Room 中使用协程时,它会在专用线程池中执行阻塞调用,因此您不必使用一个 ( Dispatchers.IO),在主线程/调度程序中调用是安全的。此答案中的更多信息为什么 querySkuDetails 需要在 IO 上下文中运行?.
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(/* Dispatchers.IO */).launch {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)
  1. 最后也是最不重要的一点,如果你创建了一个之后CoroutineScope没有取消它,你应该只是GlobalScope用来减少分配的数量。人们说“避免使用GlobalScope.”,这是真的,但你同样应该避免CoroutineScope在不取消它们的情况下创建s(我认为它更糟)。
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    GlobalScope.launch(Dispatchers.IO) {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)

TL; 博士

我已经对我的答案进行了结构化,以便您可以在准备好时独立应用每个更改。如果您只想要所有更改的总和,那么就在这里。

@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile

suspend fun getUserFromDB(): Profile {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    val userProfile = profileDao.getUserProfile()
    return userProfile
}
Run Code Online (Sandbox Code Playgroud)

快乐编码。:)