【发布时间】:2020-07-06 00:49:42
【问题描述】:
我有一个StateFlow 协程,它在我的应用程序的各个部分之间共享。当我cancelCoroutineScope 的下游收集器时,JobCancellationException 被传播到StateFlow,它停止为所有当前和未来的收集器发出值。
StateFlow:
val songsRelay: Flow<List<Song>> by lazy {
MutableStateFlow<List<Song>?>(null).apply {
CoroutineScope(Dispatchers.IO)
.launch { songDataDao.getAll().distinctUntilChanged().collect { value = it } }
}.filterNotNull()
}
我的代码中的典型“演示者”实现了以下基类:
abstract class BasePresenter<T : Any> : BaseContract.Presenter<T> {
var view: T? = null
private val job by lazy {
Job()
}
private val coroutineScope by lazy { CoroutineScope( job + Dispatchers.Main) }
override fun bindView(view: T) {
this.view = view
}
override fun unbindView() {
job.cancel()
view = null
}
fun launch(block: suspend CoroutineScope.() -> Unit): Job {
return coroutineScope.launch(block = block)
}
}
BasePresenter 实现可能会调用 launch{ songsRelay.collect {...} }
当presenter解绑时,为了防止泄露,我取消了父job。任何时候收集songsRelayStateFlow 的演示者未绑定,StateFlow 本质上以JobCancellationException 终止,并且没有其他收集器/演示者可以从中收集值。
我注意到我可以改为调用job.cancelChildren(),这似乎可行(StateFlow 不能与JobCancellationException 一起完成)。但是后来我想知道如果我不能取消工作本身,那么声明父母job 有什么意义。我可以完全删除 job,然后调用 coroutineScope.coroutineContext.cancelChildren() 达到同样的效果。
如果我只是打电话给job.cancelChildren(),就够了吗?我觉得如果不打电话给coroutineScope.cancel() 或job.cancel(),我可能没有正确或完全清理我已经开始的任务。
我也不明白为什么JobCancellationException 在调用job.cancel() 时会向上传播。 job 不是这里的“父母”吗?为什么取消它会影响我的StateFlow?
【问题讨论】:
-
我玩了一下,做了一个小单元测试,试图重现你的行为。但我无法获得
JobCancellationException。经过几年的 RxJava,我刚刚开始使用协程 + 流,所以也许我错过了一些东西。你能分享一个独立的例子来重现这个问题吗? -
是的,很可能这里还有其他东西在起作用。我会看看我是否可以在 Kotlin 操场上重现该问题
标签: android kotlin kotlin-coroutines