我一直在经历 this代码实验室以了解协程。我仍然不清楚的一件事是,为什么我们需要更改调度程序以确保我们不会阻塞主/UI 线程?如果协程是轻量级线程,那么当我已经在主线程上时,为什么我不能在协程中调用线程阻塞函数(无论它们是否挂起)?
代码实验室解释说(总结)如果我写这段代码:
// Repository.kt
suspend fun repoRefreshTitle() {
delay(500)
}
//ViewModel.kt
fun vmRefreshTitle() {
viewModelScope.launch {
_spinner.value = true
repository.repoRefreshTitle()
}
}
...那么这不会阻塞主线程。 delay()
是一个suspend
函数,因此由viewmodelScope.launch
创建的协程将暂停,直到 500 毫秒过去。但是主线程不会被阻塞。
但是,如果我将 repoRefreshTitle()
重构为以下内容:
suspend fun repoRefreshTitle() {
val result = nonSuspendingNetworkCall()
}
...那么该网络调用实际上将在主线程上完成。那是对的吗?我将不得不更改为另一个调度程序以将工作卸载到 IO 线程:
suspend fun repoRefreshTitle() {
withContext(Dispatchers.IO) {
val result = nonSuspendingNetworkCall()
}
}
我一定是在某种程度上过度简化了这一点。难道我已经在协程中就够了吗?为什么我必须切换调度程序?
最佳答案
The codelab explains that (in summary) if I write this code...then this won't block the main thread. delay() is a suspend function, so the coroutine created by viewmodelScope.launch will be paused until the 500ms passes. The main thread won't be blocked though.
正确。但是,delay()
中几乎没有真正的“工作”将在主应用程序线程上执行,因为 viewModelScope.launch()
的默认调度程序是基于Dispatchers.Main
。
However, if I refactor repoRefreshTitle() to the following...then that network call will actually be done on the main thread. Is that correct?
正确。 nonSuspendingNetworkCall()
与 delay()
一样,将在主应用程序线程上运行。在 nonSuspendingNetworkCall()
中,这不是一件好事。
I would have to change to another dispatcher to offload the work to an IO thread
正确。更具体地说,您需要使用一个使用后台线程的调度程序。对于 I/O,Dispatchers.IO
是一个常见的选择。
Isn't the fact that I'm already in a coroutine enough? Why do I have to switch the dispatcher?
因为我们不想在主应用程序线程上进行网络 I/O。 Dispatchers.Main
在主应用程序线程上运行其协程,这是 viewModelScope.launch()
的默认调度程序。这就是为什么在我写的很多东西中,我专门编写 viewModelScope.launch(Dispatchers.Main)
的原因之一——这更冗长(并且在技术上与默认设置略有不同),但它是对读者来说更明显。
关于android - 为什么需要更改协程中的调度程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61977672/