主作用域 vs 全局作用域

6

GlobalScope和MainScope有什么区别?

//Accessing data from Room
GlobalScope.launch {
            v.tvStoreName.text = pfViewModel.getStoreName()
            pageDetails.pageNumber = currentPage
            pageDetails.pageSize = pageSize
            pfViewModel.getTransactions(pageDetails, toolbarBuilder?.getDate()!!)
        }

全局作用域(GlobalScope)有时会出现非常难以重现的错误。

致命异常:android.view.ViewRootImpl$CalledFromWrongThreadException: 只有创建视图层次结构的原始线程才能触摸其视图。

MainScope().launch {
            var storeName = ""
            withContext(Dispatchers.Default) {
                storeName = pfViewModel.getStoreName()
            }
            v.tvStoreName.text = storeName
        }
1个回答

14

GlobalScopeMainScope 有什么区别?

MainScope 是一个使用默认的 Dispatchers.Main 调度器的 CoroutineScope,它绑定到主 UI 线程。

GlobalScope 是一个没有调度器的 CoroutineScope,因此在此作用域中启动的协程将使用基于线程池的 Dispatchers.Default 调度器(线程池的大小根据 CPU 核心数确定)。

GlobalScope 的上下文中也没有 Job,这意味着结构化并发并不适用。在其中启动的协程永远不会自动取消,因此需要手动控制。所以通常情况下不建议使用,除非你有非常特殊的需求。

只有创建视图层次结构的原始线程才能触摸它的视图。

当你试图从主线程外部修改视图时,就会出现此错误。如果从在 GlobalScope 中启动的协程中进行操作,则会发生这种情况(因为它是由单独的线程池支持的)。

在第二个代码段中,你使用了 withContext(Dispatchers.Default),这仅使此部分代码在该线程池上运行,但其余部分在 UI 线程上运行。这就是为什么在那里更新 UI 是可行的原因。

请注意,Room已经使用了一个带有后台线程池的调度程序来处理其查询,因此您不需要像这样手动切换上下文,只需从UI线程调用即可。
顺便提一下:像这样使用MainScope().launch { .. }是个坏主意,因为它遭受与GlobalScope相同的取消问题。要正确使用它,您需要将该作用域提取到变量/属性中,以便在适当时取消它。话虽如此,在现有作用域中使用更容易。Android在具有生命周期的组件(例如Activity)中已经提供了一个可用的协程范围(请参见lifecycle-runtime-ktx库)。它被称为lifecycleScope。您应该在此作用域中启动协程,以便在销毁活动时自动取消它们。

换句话说,我最好在这种情况下使用lifecycleScope吗?由于某些原因,在该项目中当前的Room实现不允许像在主线程中那样访问数据。 - Bitwise DEVS
是的,你应该优先选择lifecycleScope(或者根据你所在的组件选择viewModelScope)。 - Joffrey
跟进问题,如果我不在任何作用域中,比如viewModelScopelifecycleScope,怎么办?比如协程是在自定义视图库上实现的? - Bitwise DEVS
一般情况下,在这种情况下最好使用暂停函数或流。但如果您真的需要启动长期运行的协程,您可以创建一个自定义范围,并适当地使用您的自定义类具有的任何生命周期来取消它。 - Joffrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接