使用异步和等待从房间数据库获取数据

Get data from room db using async & await

我正在尝试使用 Coroutine Scope 中的 async & await 从房间数据库中获取数据,但在 return 值时出现问题。

这是我的代码:

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

道:

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

这里我想从方法中 return userProfile 但我不能在范围内这样做,如果我从 Coroutine scope.[=15= 外部 return 它将为空]

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

正如评论中指出的那样:非挂起函数永远不会 return 异步。使用下一种方法从 DB:

获取 Profile
suspend fun getUserFromDB(): Profile {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    return profileDao.getUserProfile().await()
}

使用 CoroutineScopeDispatchers.Main 上下文启动协程,获取 Profile 并更新 UI:

CoroutineScope(Dispatchers.Main).launch {
    val profile = getUserFromDB()
    updateUi(profile)
}

或者更简洁,不使用getUserFromDB()函数:

CoroutineScope(Dispatchers.Main).launch {
    val profile = AppDatabase.getDatabase(context).getProfileDao().getUserProfile().await()
    updateUi(profile)
}

您可以通过在您的 DAO 中执行以下操作,从您的查询中 return 一个 Profile 数据类型:

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

不必担心在主线程上进行调用,因为 Room 在后台线程上处理查询。

现在,根据您的架构,您可以使用 launch 启动一个新协程来调用 getUserProfile,但是由于您说过您正在做一个简单的示例,您可以调用 getUserProfile 之类的这个:

来自 Activity:

fun myMethod() = lifecycleScope.launch {
    val profileDao = AppDatabase.getDatabase(this@MyActivity).getProfileDao()
    val profile = profileDao.getUserProfile()
    //Do something with profile here
}

来自片段:

fun myMethod() = viewLifecycleOwner.lifecycleScope.launch {
    val profileDao = AppDatabase.getDatabase(this@MyFragment.context).getProfileDao()
    val profile = profileDao.getUserProfile()
    //Do something with profile here
}

来自 ViewModel:

fun myMethod() = viewModelScope.launch {
    val profileDao = AppDatabase.getDatabase(getApplication<Application>().applicationContext).getProfileDao()
    val profile = profileDao.getUserProfile()
    //Do something with profile here
}

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

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

getUserFromDB 中,launchgetUserFromDB 异步执行,不会在您 return.

之前完成

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

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

现在主要问题已经解决。这里有几件事打破了惯例和准则。

  1. getUserProfile 不应该 return Deferred<Profile> (特别是如果它已经挂起),因为这是传递的不安全原语,它应该只是 return 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
}
  1. 在 Room 中使用协程时,它会在专用线程池中执行阻塞调用,因此您不必使用一个 (Dispatchers.IO),在主程序中调用是安全的 thread/dispatcher .此答案中的更多信息 .
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(/* Dispatchers.IO */).launch {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}
  1. 最后也是最不重要的一点,如果你创建了一个 CoroutineScope 而没有在以后取消它,你应该只使用 GlobalScope 来减少分配的数量。人们说“避免使用 GlobalScope。”,这是事实,但你同样应该避免创建 CoroutineScope 而不取消它们(我认为这更糟)。
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    GlobalScope.launch(Dispatchers.IO) {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}

TL;DR

我整理了我的答案,以便您可以在准备好时独立应用每项更改。 如果您只想要所有更改的总和,那么就在这里。

@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
}

编码愉快。 :)