kotlin 在有和没有挂起IO方法的情况下执行协程之间的差异

yvt65v4c  于 2022-12-04  发布在  Kotlin
关注(0)|答案(2)|浏览(120)

让我们假设我有代码:

// Android ViewModel
class MyVM(val dao: MyDao) : ViewModel() {
    fun onButtonInsert() {
        // point 1
        viewModelScope.launch {
            // point 2
            dao.insert_SuspendFun(MyData(id=1, "hello, it works"))
            // point 3
            dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))
            // point 4
        }
    }
}

// Room DAO
@Dao
interface MyDao {
    @Insert
    suspend fun insert_SuspendFun(md: MyData)
    @Insert
    fun insert_NOT_SuspendFun(md: MyData)
}

现在,当fun onButtonInsert运行时,则:
一线工程:

dao.insert_SuspendFun(MyData(id=1, "hello, it works"))

但第二行:

dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))

失败,并显示异常:

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

“唯一的区别”是fun insert_NOT_SuspendFun没有suspend关键字。
两种方法都在相同的协程中运行。
有人能解释一下引擎盖下发生了什么吗?
线程在此协程中如何工作?
为什么第一次调用使用非UI线程,而第二次调用使用UI线程?
谢谢!

q3qa4bjr

q3qa4bjr1#

Room会为DAO的实现生成代码,因此行为是基于它们的设计决策的。
non-suspend函数在你调用它的任何线程上运行,这是non-suspend函数唯一可能的运行方式。由于Google不希望人们制作在主线程上执行IO的笨拙应用,他们决定让生成的实现在后台检查线程,如果是主线程,则抛出异常。
当IO在后台线程上完成时,Room生成的suspend函数实现会挂起。这遵循了Kotlin协程约定,即suspend函数永远不应该阻塞调用它的线程。
任何挂起函数都可以在内部挂起调用代码,直到它们完成并恢复。除了调用其他挂起函数,最常见的是使用withContextcoroutineScopesuspendCoroutinesuspendCancellableCoroutine。最后两个是低级的,允许协程被挂起,然后从任何其他线程恢复。
我还没有检查生成的源代码,但是我认为很可能Room通过使用suspendCancellableCoroutine生成了suspend函数实现,并在恢复协程之前使用内部线程池运行IO工作。
另一种方法是使用withContext(Dispatchers.IO),然后在其中完成阻塞IO工作。这可能是在协程中完成阻塞工作最常见和最简单的方法。它使用IO Dispatcher(Kotlin协程提供的线程池)。
至于为什么你的协程运行在主线程上,这是因为viewModelScope的附加调度器使用主线程。你可以通过向你的launch调用传递一个特定的调度器来覆盖它,但如果你遵循惯例,这并不常见。Android有很多必须从主线程调用的函数,因此通常看起来最干净的方式是将协程保留在主调度器上,并且只使用suspend函数和withContext块来做后台工作。例外是运行阻塞代码的协程,并且不需要在主线程上做任何事情。

ma8fv8wu

ma8fv8wu2#

您正尝试在侧主线程中运行一个长时间运行的同步任务,因为它没有提到suspend关键字以使其异步。
您可以在IO线程中使用这两种方法来代替main;

viewmodelscope.launch(dispatchers.io)

相关问题