【问题标题】:Go back to main-thread inside coroutine?回到协程内的主线程?
【发布时间】:2020-07-02 08:00:28
【问题描述】:

我正在运行此代码,因为 addListenerForSingleEvent 是一个长时间运行的操作:

CoroutineScope(IO).launch {  
    userRef.addListenerForSingleValueEvent(object : ValueEventListener {
        override fun onCancelled(p0: DatabaseError) {

        }

        override fun onDataChange(p0: DataSnapshot) {
            if (p0.exists()) {
                withContext(Main) {
                    toggleLoading()
                    val intent = Intent(this@LogInActivity, MainActivity::class.java)
                    startActivity(intent)
                    finish()
                }
            } else{
                withContext(Main) {
                    var addUsernameIntent = Intent(this@LogInActivity, 
                                             AddUsernameActivity::class.java)
                    startActivityForResult(addUsernameIntent, CHOOSE_USERNAME_REQUEST)
                }
            }
        }
   })
}   

我在写 withContext(Main) 的地方收到一个错误,上面写着:

只能在协程体内调用挂起函数

但是我有一个协程体对吗?在我只有Thread(runnable {..}) 而不是协程之前,但我读到我不应该在除主线程之外的任何其他线程内执行意图,所以我改为协程。

【问题讨论】:

  • 首先,实际上你根本没有使用 IO 协程!您只是在其中向userRef 添加了一个侦听器,这与 IO 无关。 userRef 背后的实现定义了如何调用该函数和执行任务。其次,您在onDataChange 中调用withContext,所以它不在一个宫廷体中。
  • 我真正想做的就是在单独的线程上运行运行监听器,我该怎么做? @MohammadOmidvar
  • 首先,检查当前是否为真(大多数监听器在主线程中调用)。其次,您可以使用传统方式:Handler(Looper.getMainLooper()).post()runOnUiThread 或者如果您想要协程(基于上述方法),您可以使用@commander-tvis 所说的CoroutineScope(Dispatchers.Main).launch()
  • 为什么要把监听器封装在协程中呢?它已经在后台线程上运行

标签: firebase kotlin firebase-realtime-database firebase-authentication kotlin-coroutines


【解决方案1】:

Firebase 客户端已在单独的线程上运行任何网络和磁盘 I/O 操作。几乎不需要自己在单独的线程上运行addListenerForSingleEvent

另见:

【讨论】:

    【解决方案2】:

    匿名对象的函数可以捕获作用域的变量,但它不在封闭的协程体中。将withContext(Main) 替换为创建一个新的协程:<CoroutineScope>.launch(Main)

    【讨论】:

    • 所以我只做 CoroutineScope(main).launch { } 而不是 withContext(Main)?此外,当我开始第一个协程时尝试打印当前线程时,它仍然打印出它是它正在运行的主线程
    • 为每个协程创建一个新的范围并不是一个很好的解决方案,你可以分享它,如果你正在使用 Android UI,可以考虑使用MainScope()。协程会自动安排到线程中,但是您可以通过创建自己的调度程序来影响它。
    • 所以我不确定我是否理解,我可以写 MainScope().launch {...} 而不是 withContext(Main) 吗?当我在协程中打印当前线程的名称时,我仍然不明白为什么它会打印 Main
    • MainScope() 可以存储
    • MainScope 应该是一个代表:class MyActivity : AppCompatActivity(), CoroutineScope by MainScope()。查看CoroutineScope上的文档
    最近更新 更多