【问题标题】:Kotlin difference between CoroutineScope and withContextKotlin CoroutineScope 和 withContext 的区别
【发布时间】:2021-01-02 06:37:48
【问题描述】:

要更改函数中的线程,我使用 CoroutineScope 或 withContext。我不知道有什么区别,但使用 CourineScope 我也可以使用处理程序。

例子:

private fun removeViews(){
    CoroutineScope(Main).launch(handler){
        gridRoot.removeAllViews()
    }
}

private suspend fun removeViews(){
    withContext(Main){
        gridRoot.removeAllViews()
    }
}

我从一个在后台线程 (IO) 上工作的协程调用这个函数。有比其他更合适的吗?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    这两者实际上是完全不同的,你只是碰巧有一个你没有体验到差异的用例:

    CoroutineScope(Main).launch(handler){

    这会启动一个独立运行的并发协同程序。

    withContext(Main){

    这是一个仅在其内部的代码完成时才完成的函数,并返回其结果。这是你应该这样做的方式。

    第一种方法,CoroutineScope,还有另一个缺陷,它绕过了结构化并发。您创建了一个没有父级的临时协程范围,因此如果需要更长的时间才能完成并且您的 GUI 被删除(用户导航离开当前活动),则不会自动清理。

    您实际上不应该使用CoroutineScope(Main) 成语,我认为没有一个实例是合适的。如果你明确想避免结构化并发,写起来还是更好更干净

    GlobalScope.launch(Main + handler) {
    

    效果差不多。

    如果您想要一个适合结构化并发的并发协程,请使用

    fun CoroutineScope.removeViews() {
        launch {
            gridRoot.removeAllViews()
        }
    }
    

    注意我删除了handler 参数,子协程忽略它,因为它会将任何失败转发给它的父协程,这正是你想要的。父协程应该安装一个异常处理程序。

    【讨论】:

    • 如果我想从另一个文件(如 DialogFragment)调用挂起函数怎么办?它需要从协程中调用,所以我应该使用 GlobalScope.launch(Main + handler) 还是?
    • DialogFragment 很可能应该是它的作用域。它应该要么实现CoroutineScope,要么内部有一个val coroutineScope: CoroutineScope。例如,ViewModel 已经有了 integrated. 然后你调用 viewModelScope.launch { ... }
    • 顺便说一句,在 Android 上实现 CoroutineScope 很方便:class MyDialogFragment : DialogFragment, CoroutineScope by MainScope {
    【解决方案2】:

    来自Antonio Leiva article关于协程:

    协程上下文是一组规则和配置,它们定义 协程将如何执行

    withContext 是一个允许您轻松更改挂起函数的context 的函数,以确保该函数在特定线程中执行(例如来自 IO 池的线程)。为此,您可以强制挂起函数在特定线程池中执行其主体,例如:

    suspend fun getAuthenticationStatus(): AuthenticationStatus = withContext(Dispatchers.IO) {
        when (val result = repository.getAuthenticationStatus()) {
            is Result.Success -> result.data
            is Result.Error -> AuthenticationStatus.Unauthorized
        }
    }
    

    这样,即使您从 UI 范围 (MainScope) 调用此挂起函数,您也可以 100% 确定挂起函数是在工作线程中执行的,并且您可以使用返回的结果更新 UI主线程,如:

    MainScope().launch {
                userIdentityVM.getAuthenticationStatus().run {
                    when (this) {
                        is AuthenticationStatus.Authenticated -> {
                           // do something
                        }
                        is AuthenticationStatus.Unauthorized -> {
                           // do something else
                        }
                    }
                }
            }
    

    总而言之,通过使用withContext,您可以使您的挂起函数“Main Safe”

    scopecontext 的区别基本上是预期用途。 要启动协程,您通常使用launch coroutine builder,定义为CoroutineScope 上的扩展函数。

    fun CoroutineScope.launch(
        context: CoroutineContext = EmptyCoroutineContext,
        // ...
    ): Job
    

    在协程作用域上指定为参数的上下文通过加号运算符合并到协程作用域,并且优先在协程作用域指定的“默认”上下文上。这样您就可以在“父”上下文中执行代码。为了深入,我建议你 this article Roman Elizarov(Kotlin 库团队负责人 @JetBrains)。

    【讨论】:

      【解决方案3】:

      技术上两者是相同的,但在用例方面两者是不同的,并且对不同的用例有很大影响,所以在使用它们时要小心
      协程范围:
      CoroutineScope 是 Coroutine 的一个起点。 CoroutineScope 内部可以有多个协程,这形成了协程层次结构。 让我们想想,父母有不止一个孩子。想想CoroutineScope 是一个父母,这个父母可以有多个孩子,他们也是协程。这些孩子被称为job

      private val coroutineScope = CoroutineScope()
          coroutineScope(IO).launch{
          val childOne = launch(Main){}
         val childTwo = launch(Main){}
      }
      

      看到 childOne 和 childTwo 了吗?为什么我们需要这些?因为我们不能直接取消协程,所以无法直接取消协程,要么协程完成,要么失败。但是如果我们想取消它呢?在这种情况下,我们需要job。但要注意的是,这些工作children 与父母完全相关。父母是(IO),孩子是(主要),这个父母是在IO Disptacher中启动的,但是当涉及到那些孩子时,他们会切换到(主要)并做他们的事情,但父母仍然会在(IO)切换孩子的调度员不会影响父母。
      但是,如果其中一个孩子出了问题,会发生什么, 在这种情况下,我们将观看本次峰会:
      https://www.youtube.com/watch?v=w0kfnydnFWI
      这个关于协程异常和取消的峰会。看吧,太棒了...

      withContext:
      withContext 是什么?
      withContext 应该在任何Coroutinesuspend fun 内,因为withContext 本身就是一个挂起函数。
      withContext 用于在不同情况下切换上下文
      但是怎么做呢?

      
      suspend fun fetchFromNetworkAndUpdateUI() {
          withContext(IO){
              println("Some Fake data from network")
          }
          
          withContext(Main){
              //updating Ui
              //setting that Data to some TextView etc
          }
          
      }
      

      查看代码,我们从网络异步获取数据,因为我们不想阻止MainThread,然后我们切换上下文,为什么?因为我们无法在 IoDispatcher 中更新与 UI 相关的内容,因为我们已经使用 withContext(main){} 将上下文更改为 main 并更新 UI。
      还有其他用例,例如 liveData,我们使用 IoDispatcher 进行改造来获取值,然后在下一步中,我们必须使用 withContext(main){} 将其设置为 liveData,因为我们无法在后台线程中观察 liveData 的值。
      是的,我希望这会有所帮助。如果有任何问题,请发表评论。

      【讨论】:

        猜你喜欢
        • 2019-11-13
        • 2020-04-09
        • 2021-08-26
        • 1970-01-01
        • 2022-11-12
        • 2020-11-21
        • 2021-01-05
        • 1970-01-01
        • 2019-12-06
        相关资源
        最近更新 更多