[英]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()
}
}
在生产中,我在访问context
在doSomething(
) 中得到了许多NPEs
。
我的假设是coroutine
在onDestroy()
调用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协程与“架构组件”一起使用 。
这是预期的行为,因为协程取消本质上是合作的。
尽管暂停功能将根据您的情况检查协程的状态,但您必须自己执行此操作。
一种更干净的方法是将这些操作包含在视图模型中,而只需在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.