coolDude
coolDude

Reputation: 717

Why Do You Need to Change Dispatchers in Coroutines?

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?

The codelab explains that (in summary) if I write this code:

// 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()
}

...then that network call will actually be done on the main thread. Is that correct? I would have to change to another dispatcher to offload the work to an IO thread:

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

I must be oversimplifying this somehow. Isn't the fact that I'm already in a coroutine enough? Why do I have to switch the dispatcher?

Upvotes: 5

Views: 6200

Answers (3)

Md. Yamin Mollah
Md. Yamin Mollah

Reputation: 1791

See this documents which describes that the Dispatchers.IO is specially designed for I/O operations where

viewModelScope.launch {

}

creates a coroutine block that is lifecycle aware and suitable for any asynchronous operation not specialized for I/O operation. When your ViewModel is destroyed then

viewModelScope.launch{
    // Invoke network suspend functions from repository
    // Or any kind of asynchronous operation
}

will be stopped and cancelled which will cancel this block

withContext(Dispatchers.IO) {
    // Invoke only I/O operations
}

too because viewModelScope keeps communication with that withContext(Dispatchers.IO).

You should not make your viewModelScope busy with I/O operations instead you should let another I/O specialized coroutine thread maintain that I/O operation and keep track from viewModelScope. This will make viewModelScope more light weight.

Upvotes: 0

Arrowsome
Arrowsome

Reputation: 2859

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)

You start your work on Main Thread and switch to another coroutine for heavy work and easily get the result back on the main thread when the result is ready.

fun onSaveImageFile(source: Int, filename: String) = viewModelScope.launch {
    val isFileSaved = withContext(Dispatchers.IO) {
        FileRepository.saveImageFile(source, filename)
    }
    toastViewModel.postValue(if (isFileSaved) "Image file saved!" else "Failed to save image file!")
}

Upvotes: 3

CommonsWare
CommonsWare

Reputation: 1006614

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.

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.

However, if I refactor repoRefreshTitle() to the following...then that network call will actually be done on the main thread. Is that correct?

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

I would have to change to another dispatcher to offload the work to an IO thread

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

Isn't the fact that I'm already in a coroutine enough? Why do I have to switch the dispatcher?

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.

Upvotes: 5

Related Questions