首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么你需要在协程中改变调度器?

为什么你需要在协程中改变调度器?
EN

Stack Overflow用户
提问于 2020-05-24 03:40:39
回答 3查看 1.2K关注 0票数 2

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

codelab解释说(总而言之)如果我写这段代码:

代码语言:javascript
复制
// Repository.kt
suspend fun repoRefreshTitle() {
    delay(500)
}

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

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

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

代码语言:javascript
复制
suspend fun repoRefreshTitle() {
    val result = nonSuspendingNetworkCall()
}

...then网络调用实际上是在主线程上完成的。对吗?我将不得不切换到另一个dispatcher来将工作卸载到IO线程:

代码语言:javascript
复制
suspend fun repoRefreshTitle() {
    withContext(Dispatchers.IO) {
        val result = nonSuspendingNetworkCall()
    }
}

我一定是在某种程度上过于简单化了。难道我已经在一个协同程序中还不够吗?为什么我必须切换调度器?

EN

回答 3

Stack Overflow用户

发布于 2020-05-24 03:57:45

当您在viewModelScope中运行代码时,并不意味着您的主线程不会冻结。它只是确保如果你开始在MainThread上工作,并且你正在等待另一个线程返回结果,它不会阻塞主线程,例如调用一个带有Retrofit的应用程序接口,并等待更新你的ViewModel中的LiveData。

那么为什么你需要改变协程作用域呢?(可能使用withContext)

你在主线程上开始你的工作,然后切换到另一个协程来处理繁重的工作,当结果准备好时,很容易在主线程上得到结果。

代码语言:javascript
复制
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!")
}
票数 2
EN

Stack Overflow用户

发布于 2020-05-24 03:58:22

代码实验室

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

对,是这样。但是,delay()中真正的“工作”将在主应用程序线程上执行,因为viewModelScope.launch()的默认调度程序是基于Dispatchers.Main的。

然而,如果我将repoRefreshTitle()重构为following...then,那么网络调用实际上将在主线程上完成。对吗?

对,是这样。与delay()一样,nonSuspendingNetworkCall()将在主应用程序线程上运行。在nonSuspendingNetworkCall()中,这不是一件好事。

我必须切换到另一个调度器,以便将工作卸载到IO线程

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

是不是我已经在协程中的事实还不够?为什么我必须切换调度器?

因为我们不想在主应用程序线程上执行网络I/O。Dispatchers.Main在主应用程序线程上运行它的协程,这是viewModelScope.launch()的默认调度程序。这就是为什么,在我写的很多东西中,我特别写viewModelScope.launch(Dispatchers.Main)的原因之一--它更冗长(在技术上与默认的略有不同),但它对读者来说更明显。

票数 2
EN

Stack Overflow用户

发布于 2020-05-24 04:15:33

请参阅此documents,其中描述了Dispatchers.IO是专门为I/O操作设计的,其中

代码语言:javascript
复制
viewModelScope.launch {

}

创建一个可识别lifecycle的协程块,该块适用于任何不专用于I/O操作的异步操作。当你的ViewModel被销毁的时候

代码语言:javascript
复制
viewModelScope.launch{
    // Invoke network suspend functions from repository
    // Or any kind of asynchronous operation
}

将被停止并取消,这将取消此阻止

代码语言:javascript
复制
withContext(Dispatchers.IO) {
    // Invoke only I/O operations
}

这也是因为viewModelScope保持与该withContext(Dispatchers.IO)的通信。

您不应该让viewModelScope忙于I/O操作,而应该让另一个专门的I/O协程线程维护该I/O操作,并跟踪viewModelScope。这将使viewModelScope更加轻量级。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61977672

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档