【问题标题】:Coroutine launch() while inside another coroutine在另一个协程中的协程启动()
【发布时间】:2021-12-28 08:05:13
【问题描述】:

我有以下代码(伪代码)

fun onMapReady()
{
    //do some stuff on current thread (main thread)

    //get data from server
    GlobalScope.launch(Dispatchers.IO){

        getDataFromServer { result->

            //update UI on main thread
            launch(Dispatchers.Main){
                updateUI(result) //BREAKPOINT HERE NEVER CALLED
            }
        }

    }
}

正如评论中所说,代码永远不会进入主队列的协程调度。但是,如果我明确使用 GlobalScope.launch(Dispatchers.Main) 而不仅仅是 launch(Dispatchers.Main)

fun onMapReady()
{
    //do some stuff on current thread (main thread)

    //get data from server
    GlobalScope.launch(Dispatchers.IO){

        getDataFromServer { result->

            //update UI on main thread
            GlobalScope.launch(Dispatchers.Main){
                updateUI(result) //BREAKPOINT HERE IS CALLED
            }
        }

    }
}

为什么第一种方法不起作用?

【问题讨论】:

    标签: kotlin kotlin-coroutines coroutinescope


    【解决方案1】:

    @broot 已经解释了问题的要点。您正在尝试 launch 外部 GlobalScope.launch 的子范围内的协程,但是当调用 getDataFromServer 的回调时,该范围已经完成。

    因此,简而言之,不要在将在您无法控制的地点/时间调用的回调中捕获外部范围。

    处理您的问题的一个更好的方法是让getDataFromServer 暂停而不是基于回调。如果它是您无法控制的 API,您可以通过这种方式创建一个挂起包装器:

    suspend fun getDataFromServerSuspend(): ResultType = suspendCoroutine { cont ->
        getDataFromServer { result ->
            cont.resume(result)
        }
    }
    

    然后您可以简化调用代码:

    fun onMapReady() {
    
        // instead of GlobalScope, please use viewModelScope or lifecycleScope,
        // or something more relevant (see explanation below)
        GlobalScope.launch(Dispatchers.IO) {
    
            val result = getDataFromServer()
    
            // you don't need a separate coroutine, just a context switch
            withContext(Dispatchers.Main) {
                updateUI(result)
            }
        }
    }
    

    附带说明,GlobalScope 可能不是您想要的,在这里。相反,您应该使用映射到视图或视图模型的生命周期的范围(viewModelScopelifecycleScope),因为如果视图被销毁,您对此协程的结果不感兴趣(所以它应该被取消)。如果由于某种原因在协程内部挂起或循环,这将避免协程泄漏。

    【讨论】:

      【解决方案2】:

      我认为这里的问题是getDataFromServer() 是异步的,它会立即返回,因此您在退出GlobalScope.launch(Dispatchers.IO) { ... } 块后调用launch(Dispatchers.Main)。换句话说:您尝试使用已经完成的协程范围来启动协程。

      我的建议是不要将异步、基于回调的 API 与这样的协程混合。协程最适用于同步的挂起函数。此外,如果您更喜欢独立于其他任务异步执行所有内容(您的onMapReady() 启动了 3 个单独的异步操作),那么我认为协程根本不是一个好的选择。

      谈到你的例子:你确定你不能直接从主线程执行getDataFromServer() 吗?它不应该阻塞主线程,因为它是异步的。同样,在某些库中,回调会在主线程中自动执行,在这种情况下,您的示例可以替换为:

      fun onMapReady() {
          getDataFromServer { result->
              updateUI(result)
          }
      }
      

      如果结果在后台线程中执行,那么您可以像以前一样使用GlobalScope.launch(Dispatchers.Main),但这并不是我们使用协程的常用方式。或者您可以使用实用程序,例如runOnUiThread() 在 Android 上可能更有意义。

      【讨论】:

      • 有道理,我没有考虑到 getDataFromServer 已经是异步的,因此不需要包装在协程中。干杯
      猜你喜欢
      • 1970-01-01
      • 2018-11-21
      • 1970-01-01
      • 2020-04-12
      • 2017-12-11
      • 1970-01-01
      • 1970-01-01
      • 2020-07-15
      • 1970-01-01
      相关资源
      最近更新 更多