我特别关心将用户启动的数据插入本地数据库。
以下模式在将Kotlin协程与[Android Architecture Components] ViewModels结合使用的示例(包括来自JetBrains,Google / Android等官方来源)中很普遍。
class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
fun thisIsCalledFromTheUI() = launch {
/* do some UI stuff on the main thread */
withContext(Dispatchers.IO) {
try {
/* do some IO, e.g. inserting into DB */
} catch (error: IOException) {
/* do some exception handling */
}
}
}
}
我对documentation的理解是,在上面的示例中,coroutineContext
被销毁时,在UI上下文中启动的协程(通过ViewModel
定义)将被取消,但是withContext(Dispatchers.IO)
块中的代码将运行完毕。
但是,在我从全局范围(启动/异步)协同程序模型(1.0.0之前)重构项目之前,我觉得我需要澄清一些事情:
我对文档的阅读是否正确?还是在withContext(Dispatchers.IO)
块运行完成之前销毁视图模型也会触发该作业的取消?即该模型可以用于将数据插入我的数据库,还是在用户回击或导致ViewModel所有者关闭以致最终丢失数据的情况下出现一些奇怪的计时问题?
我不想无意中引入计时错误,因为我误解了一些东西,因此将我的代码转换为类似于上面显示的模型。
因此,我决定进行一些测试,在我看来,所有使用此模型写入数据库的示例都可能存在基本错误。
修改代码以记录发生的情况,例如:
class ChildViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
Log.d("onCleared", "Start")
_job.cancel()
Log.d("onCleared", "End")
}
fun thisIsCalledFromTheUI() = launch {
Log.d("thisIsCalledFromTheUI", "Start")
GlobalScope.launch(Dispatchers.IO) {
Log.d("GlobalScope", "Start")
delay(15000)
Log.d("GlobalScope", "End")
}
withContext(Dispatchers.IO) {
Log.d("withContext", "Start")
delay(10000)
Log.d("withContext", "End")
}
Log.d("thisIsCalledFromTheUI", "End")
}
}
如果您让它运行到完成,则会导致以下结果:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/withContext: End
D/thisIsCalledFromTheUI: End
D/GlobalScope: End
但是,如果您在withContext
结束之前关闭“片段/活动”(不是应用程序),则会得到以下提示:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/GlobalScope: End
至少向我表明,您不能使用它来将非瞬态数据写入数据库。
答案 0 :(得分:1)
我对文档的理解是,在以上示例中,在
coroutineContext
被销毁时,在UI上下文中启动的协程(通过ViewModel
定义)将被取消,但是{ {1}}块将运行完毕。
这不是对文档的正确阅读。 withContext(Dispatchers.IO)
不会启动另一个协程,它只是在其块的持续时间内更改当前协程的上下文。因此,该协程以及您启动的所有其他协程将被取消,而不会提供与之关联的具有不同工作(或根本没有工作,例如withContext
)的新父上下文。
但是,您建议使用GlobalScope
进行持久性操作的想法只是针对您正在测试的场景的本地补丁,您仍然无法保证它会完成。用户可以完全退出应用程序,而Android可以终止该过程。
因此,如果您的目标是构建真正可靠的应用程序,则必须适应以下事实:在协程完成之前,不会将任何信息写入DB。希望您在数据库事务中运行该操作,如果您的程序被杀死,该事务将自动回滚,否则将无法防止不一致。