我正在探索在Android UI线程的上下文中使用协同例程。我按照{{3}}中的说明实施了contextJob
。后台工作从GUI开始,我想在每次点击时重新启动它(停止当前运行的并重新启动它)。
但是一旦被取消的工作不能重复使用,所以即使创建了一份儿童工作:
val job = Job(contextJob)
并取消它并没有帮助,因为它必须被重新分配。
有没有办法重用Job实例?
答案 0 :(得分:5)
Job的设计生命周期非常简单。它"已完成" state是 final ,非常类似于" Destroyed" Android Activity
的状态。因此,父母Job
最好与Activity
相关联,如指南中所述。当且仅当活动被销毁时,您应该取消父作业。由于被破坏的活动无法重复使用,因此您永远不会遇到重复使用其工作的需要。
每次点击开始工作的推荐方法是使用actor,因为它们可以帮助您避免不必要的并发。该指南显示了如何在每次单击时启动它们,但它没有显示如何取消当前正在运行的操作。
您需要与Job
结合使用withContext
的全新实例,以便将代码块与其他所有代码分开取消:
fun View.onClick(action: suspend () -> Unit) {
var currentJob: Job? = null // to keep a reference to the currently running job
// launch one actor as a parent of the context job
// actor prevent concurrent execution of multiple actions
val eventActor = actor<Unit>(contextJob + UI, capacity = Channel.CONFLATED) {
for (event in channel) {
currentJob = Job(contextJob) // create a new job for this action
try {
// run an action within its own job
withContext(currentJob!!) { action() }
} catch (e: CancellationException) {
// we expect it to be cancelled and just need to continue
}
}
}
// install a listener to send message to this actor
setOnClickListener {
currentJob?.cancel() // cancel whatever job we were doing now (if any)
eventActor.offer(Unit) // signal to start next action when possible
}
}
演员在其父作业(附加到活动)被取消之前始终处于活动状态。演员等待点击并在每次点击时开始action
。但是,action
的每次调用都会使用Job
块封装到自己的withContext
中,以便可以与其父作业分开取消。
请注意,此代码适用于不可取消或只需要一些时间取消的操作。操作可能需要在取消时清理其资源,并且,因为此代码使用了actor,所以它确保在下一个操作开始之前完成上一个操作的清理。
答案 1 :(得分:0)
Android Developers 官方博客有一篇关于如何处理此类用例的好文章,以及一些实用程序类以线程安全的方式封装逻辑:
https://medium.com/androiddevelopers/coroutines-on-android-part-iii-real-work-2ba8a2ec2f45