为什么需要更改协程中的调度程序?

coo*_*ude 4 android kotlin-coroutines

我一直在通过这个Codelab 来了解协程。我仍然不清楚的一件事是,为什么我们需要更改调度程序以确保我们不会阻塞主/UI 线程?如果协程是轻量级线程,那么当我已经在主线程上时,为什么我不能在协程中调用线程阻塞函数(无论它们是否挂起)?

Codelab 解释说(总而言之)如果我编写此代码:

// Repository.kt
suspend fun repoRefreshTitle() {
    delay(500)
}

//ViewModel.kt
fun vmRefreshTitle() {
   viewModelScope.launch {
       _spinner.value = true
       repository.repoRefreshTitle()
   }
}

Run Code Online (Sandbox Code Playgroud)

...那么这不会阻塞主线程。delay()是一个suspend函数,因此创建的协程viewmodelScope.launch将暂停,直到 500ms 过去。但主线程不会被阻塞。

但是,如果我重构repoRefreshTitle()为以下内容:

suspend fun repoRefreshTitle() {
    val result = nonSuspendingNetworkCall()
}
Run Code Online (Sandbox Code Playgroud)

...然后该网络调用实际上将在主线程上完成。那是对的吗?我必须更改为另一个调度程序才能将工作卸载到 IO 线程:

suspend fun repoRefreshTitle() {
    withContext(Dispatchers.IO) {
        val result = nonSuspendingNetworkCall()
    }
}
Run Code Online (Sandbox Code Playgroud)

我一定是把这个问题过于简单化了。我已经处于协程中还不够吗?为什么我必须更换调度员?

Com*_*are 5

Codelab 解释说(总而言之)如果我编写此代码...那么这不会阻塞主线程。delay() 是一个挂起函数,因此 viewmodelScope.launch 创建的协程将暂停,直到 500ms 过去。但主线程不会被阻塞。

正确的。然而,其中真正的“工作”很少,delay()将在主应用程序线程上执行,因为默认的调度程序viewModelScope.launch()是基于Dispatchers.Main.

但是,如果我将 repoRefreshTitle() 重构为以下内容......那么该网络调用实际上将在主线程上完成。那是对的吗?

正确的。nonSuspendingNetworkCall(),例如delay(),将在主应用程序线程上运行。在nonSuspendingNetworkCall(),这不是什么好事。

我必须更改为另一个调度程序才能将工作卸载到 IO 线程

正确的。更具体地说,您需要使用使用后台线程的调度程序。对于I/O,Dispatchers.IO是一个常见的选择。

我已经处于协程中还不够吗?为什么我必须更换调度员?

因为我们不想在主应用程序线程上进行网络 I/O。Dispatchers.Main在主应用程序线程上运行其协程,这是viewModelScope.launch(). 这就是为什么在我写的很多内容中,我专门写的原因之一viewModelScope.launch(Dispatchers.Main)——这更冗长(并且在技术上与默认值略有不同),但对读者来说更明显。