繁体   English   中英

关于 Kotlin 协程取消的问题

[英]Question about Kotlin Coroutine Cancellation

我的应用程序中有一个类似于以下的代码

class MyFragment : Fragment(), CoroutineScope by MainScope() {

    override fun onDestroy() {
        cancel()
        super.onDestroy()
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        doSomething()
    }

    private fun doSomething() = launch {
        val data = withContext(Dispathers.IO) {
            getData()
        }

        val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
        pref.edit().putBoolean("done", true).apply()
    }
}

在生产中,我在访问contextdoSomething( ) 中得到了许多NPEs

我的假设是coroutineonDestroy()调用cancel()后被cancel() ,所以我没有费心检查空值的context 但看起来即使在调用cancel()之后continues执行。 我认为如果在完成withContext和恢复coroutines之前调用cancel()会发生这种情况。

所以我用以下替换了doSomething()

    private fun doSomething() = launch {
        val data = withContext(Dispathers.IO) {
            getData()
        }

        if (isActive) {
            val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
            pref.edit().putBoolean("done", true).apply()
        }
    }

这修复了崩溃。

但是,这是预期的行为还是我做错了什么? Kotlin 的文档对此不是很清楚。 而且网上的大部分例子都像我原来的代码。

您的代码假定withContext()返回时将停止执行(如果取消了作用域,但实际上并没有取消),直到kotlin coroutines 1.3.0版本。 这是GitHub 问题 我猜您正在使用该库的早期版本。

我还建议您使用LifecycleScope而不是自定义范围。 它是lifecycle-runtime-ktx库的一部分。 因此,简化的解决方案如下所示:

// build.gradle
dependencies {
    ...
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02"
}
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        doSomething()
    }

    private fun doSomething() = viewLifecycleOwner.lifecycleScope.launch {
        val data = withContext(Dispathers.IO) {
            getData()
        }

        val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
        pref.edit().putBoolean("done", true).apply()
    }
}

还有其他有用的实用程序可以简化协程的使用, 请参阅将Kotlin协程与“架构组件”一起使用

这是预期的行为,因为协程取消本质上是合作的。

参见https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#cancellation-is-cooperative

尽管暂停功能将根据您的情况检查协程的状态,但您必须自己执行此操作。

一种更干净的方法是将这些操作包含在视图模型中,而只需在ViewModel onCleared取消协程; 如果您使用的是ViewModel

最新的协程更新已引入了使用示波器安全工作的可能性。 基本上,如果您要在Fragments或Activity中进行异步工作,请使用lifecycleScope ;如果要在ViewModel中进行异步工作,则需要使用viewModelScope 还有一个GlobalScope可以在任何类中使用,但事实证明它具有许多稳定性缺陷,应避免使用。 在任何情况下,都应使用lifecycleScope或viewModelScope并在其他类中使用暂停函数。

因此,这是执行此操作的方法:

 override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    lifecycleScope.launch {
        doSomething()
    }
 }

 private suspend fun doSomething(){
    withContext(Dispatchers.IO){
        getData()
        if (isActive) {
            val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
            pref.edit().putBoolean("done", true).apply()
        }
    }
 }

如您在上面的代码中看到的,doSomething函数不需要作用域或启动,因为它充当在onActivityCreated中已启动的协程中执行的任务。 在doSomething()函数中启动新的协程是没有意义的。 同样,您可以将整个代码封装在withContext()语句中,因为获取共享首选项也是在Dispatchers.IO上运行效果最好的事务。 或者,您可以随时将上下文更改为所需的上下文,例如:

   withContext(Dispatchers.IO){
        getData()
        if (isActive) {
            val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
            pref.edit().putBoolean("done", true).apply()
        }
        withContext(Dispatchers.Default){
            // Heavy computing
        }
    }

永远记住,Dispatchers.IO针对数据库事务,共享首选项和网络调用进行了最佳优化。 如果您在forEach中执行诸如forEach之类的繁重计算以及诸如此类的工作,请使用Dispatchers.Default;如果需要进行查看工作,请使用Dispatchers.Main。

这些作用域确保您的任务被执行而不会被其他活动组件中断,并且如果它被活动破坏之类的事物中断,它将防止异常并在范围内的所有内容上正确使用垃圾回收器。 自从我开始使用示波器以来,再也没有崩溃的机会了。

由于瓦列里·卡特科夫(Valery Katkov)的答案是正确的,但是您可以这样做,而无需协程取消:

private fun doSomething() = launch {
        val data = withContext(Dispathers.IO) {
            getData()
        }

        val pref = context?.getSharedPreferences("mypref", MODE_PRIVATE) ?: return@launch
        pref.edit().putBoolean("done", true).apply()
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM