为什么需要更改协同程序中的分派器?

I've been going through this codelab to learn about coroutines. One thing that still isn't clear to me is that why do we need to change dispatchers to ensure that we don't block the main/UI thread? If coroutines are light-weight threads, then why can't I invoke thread-blocking functions (whether they're suspending or not) within a coroutine when I'm already on the main thread?

如果我编写以下代码,则代码实验室对此进行了总结:

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

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

...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.

However, if I refactor repoRefreshTitle() to the following:

suspend fun repoRefreshTitle() {
    val result = nonSuspendingNetworkCall()
}

...然后网络调用实际上将在主线程上完成。那是对的吗?我将不得不更改为另一个调度程序,以将工作分流到IO线程:

suspend fun repoRefreshTitle() {
    withContext(Dispatchers.IO) {
        val result = nonSuspendingNetworkCall()
    }
}

我必须以某种方式过分简化。我已经足够接受协程了吗?为什么必须切换调度程序?

评论
  • znon
    znon 回复

    When you run your code inside viewModelScope it doesn't mean your main thread won't freeze. It just ensures if you started work on MainThread and you are waiting for another thread to return a result, it won't block the main thread, e.g. calling an API with Retrofit and waiting to update LiveData in your ViewModel.

    So Why do you need to change Coroutine Scope? (probably using withContext)

    您开始在主线程上工作,然后切换到另一个协程进行繁重的工作,并轻松地将结果返回到主线程上。

  • gcum
    gcum 回复

    代码实验室对此进行了总结(总结),如果我编写此代码...那么这不会阻塞主线程。 delay()是一个暂停函数,因此viewmodelScope.launch创建的协程将被暂停,直到经过500ms。但是主线程不会被阻塞。

    Correct. However, what little real "work" there is in delay() will be performed on the main application thread, because the default dispatcher for viewModelScope.launch() is based on Dispatchers.Main.

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

    Correct. nonSuspendingNetworkCall(), like delay(), will be run on the main application thread. In the nonSuspendingNetworkCall(), that's not a good thing.

    我将不得不更改为另一个调度程序以将工作分流到IO线程

    Correct. More specifically, you need to use a dispatcher that uses a background thread. For I/O, Dispatchers.IO is a common choice.

    我已经足够接受协程了吗?为什么必须切换调度程序?

    Because we do not want to do network I/O on the main application thread. Dispatchers.Main runs its coroutines on the main application thread, and that is the default dispatcher for viewModelScope.launch(). That's one of the reasons why, in a lot of what I write, I specifically write viewModelScope.launch(Dispatchers.Main) — that's more wordy (and technically slightly different than the default), but it is more obvious to readers.