【问题标题】:Get data from room db using async & await使用 async & await 从房间数据库中获取数据
【发布时间】:2020-06-24 11:15:05
【问题描述】:

我正在尝试使用 async & await 内部的 Coroutine Scope 从房间数据库中获取数据,但在返回值时遇到问题。

这是我的代码:

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>

这里我想从方法中返回 userProfile,但我不能在范围内这样做,如果我从 Coroutine scope 之外返回,它将为空。

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

【问题讨论】:

  • 你根本做不到,一个非挂起函数永远不可能异步返回,你可能需要使函数挂起,否则使用runBlocking阻塞返回结果。
  • @AnimeshSahu 你能详细说明一下吗?
  • 下面的答案已经解释过了:)

标签: android kotlin android-room kotlin-coroutines deferred


【解决方案1】:

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

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

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

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

  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 不应该返回 Deferred&lt;Profile&gt;(尤其是如果它已经挂起),因为这是一个不安全的传递原语,它应该只返回 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),在主线程/调度程序中调用是安全的。此答案Why does the querySkuDetails need to run in IO context? 中的更多信息。
fun getUserFromDB() {
    val profileDao = AppDatabase.getDatabase(context).getProfileDao()
    CoroutineScope(/* Dispatchers.IO */).launch {
        val userProfile = profileDao.getUserProfile().await()
    }
    return userProfile
}
  1. 最后一点,如果你创建了一个CoroutineScope 之后没有取消它,你应该只使用GlobalScope 来减少分配的数量。人们说“避免使用GlobalScope。”,这是真的,但你同样应该避免创建CoroutineScopes而不取消它们(我认为它更糟)。
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
}

愉快的编码。 :)

【讨论】:

  • 工作完成后如何取消Coroutine Scope?
  • CoroutineScope 上有一个.cancel() 方法。
  • 真棒和完整的答案。谢谢@DominicFischer
  • 你确定 await 可用吗?
  • 是Deferred上定义的方法。
【解决方案2】:

正如 cmets 中所指出的:非挂起函数永远不能异步返回。使用下一种方法从 DB 中获取 Profile:

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

使用CoroutineScope 和Dispatchers.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)
}

【讨论】:

  • await 会等到任务完成吗?任务完成后返回结果。是这样吗?
  • 您提到的第一种方法不是在主线程上运行还是 Select Query 将在后台处理它?
  • 协程会在主线程中运行,getUserFromDB()是一个挂起函数,它会挂起协程而不阻塞主线程。 'updateUi(profile)' 将在主线程中运行。
  • @Sergey 使用 await 时 getUserProfile() 的返回类型应该是什么?
  • @SumitShukla,应该是Deferred&lt;Profile&gt;。
【解决方案3】:

您可以通过在 DAO 中执行以下操作从查询中返回 Profile 数据类型:

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

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

现在,根据您的架构,您可以使用launch 启动一个新的协程来调用getUserProfile,但既然您说您正在做一个简单的示例,您可以像这样调用getUserProfile:

来自活动:

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
}

【讨论】:

  • 配置文件变量是否会为空,因为查询将在后台线程中运行。
  • @SumitShukla getUserProfile() 是一个暂停呼叫,一旦 Room 响应就会恢复。
  • @SumitShukla 有什么用?还是怎么做?
猜你喜欢
  • 2020-09-09
  • 2022-01-22
  • 1970-01-01
  • 2018-08-27
  • 1970-01-01
  • 2020-02-02
  • 2020-12-02
  • 2019-04-14
  • 1970-01-01
相关资源
最近更新 更多