我的视图模型有几个函数可以运行启动协程的长时间后台操作。在用户界面中,我有一个微调器,至少在此类后台进程处于活动状态时应显示该微调器。实现这种旋转器的最佳方法是什么?
我通过使用计数器和 invokeOnCompletion 实现了这一点,但是由于某种原因该处理程序没有调用。
型号:
class DeviceListViewModel() : ViewModel() {
private val _updating = Updating(viewModelScope)
val updating = _updating.state
fun refresh1(){
viewModelScope.launchUpdating{
}
}
fun refresh2(){
viewModelScope.launchUpdating{
}
}
private fun CoroutineScope.launchUpdating(block: suspend CoroutineScope.() -> Unit) =
launchUpdating(this@launchUpdating, _updating, block)
下面代码的问题是,由于某种原因,作业完成处理程序永远不会被调用。
/**
* Allows to monitor running of multiple jobs
* If at least one of jobs is active then it state value is TRUE
* As only all job are complete state should be changed to FALSE automatically
*/
class Updating(private val scope: CoroutineScope) {
private val isUpdating = MutableStateFlow(false)
@Volatile private var counter = 0
private val mutex = Mutex()
val state = isUpdating.asStateFlow()
private fun monitorJob(job: Job) {
scope.launch {
mutex.withLock{
counter++
if (counter > 0){
isUpdating.value = true
}
job.invokeOnCompletion(this@Updating::onJobCompletion)
}
}
}
@Suppress("UNUSED_PARAMETER")
private fun onJobCompletion(th:Throwable?) {
scope.launch {
mutex.withLock{
counter--
if (counter < 0){
counter = 0
}
if (counter == 0){
isUpdating.value = false
}
}
}
}
companion object {
fun launchUpdating(scope: CoroutineScope, updating: Updating, block: suspend CoroutineScope.() -> Unit) =
scope.launch(block = block).also { updating.monitorJob(it) }
}
}
由于嵌套协程的启动,可能会出现竞争。启动
block
的协程可能在调用 job.invokeOnCompletion
之前完成运行。
如果你稍微简化一下(没有经过严格测试,但似乎有效):
// Copyright 2023 Google LLC.
// SPDX-License-Identifier: Apache-2.0
class Monitor(private val scope: CoroutineScope) {
private val _isUpdating = MutableStateFlow(false)
@OptIn(FlowPreview::class)
val isUpdating = _isUpdating.asStateFlow().debounce(500)
private val mutex = Mutex()
private var count = 0
set(value) {
field = value
_isUpdating.value = (count > 0)
}
fun launch(block: suspend CoroutineScope.() -> Unit) {
scope.launch {
try {
mutex.withLock { count++ }
block()
} finally {
mutex.withLock { count-- }
}
}
}
}
class DeviceListViewModel: ViewModel() {
private val monitor = Monitor(viewModelScope)
val updating = monitor.updating
fun refresh1() {
monitor.launch {
// ...
}
}
}
这可能会使调试和推理变得更容易。
注意:我在
debounce
流中添加了 updating
,这样除非任务花费的时间超过 500 毫秒,否则微调器不会出现。减少短任务中旋转器闪烁的机会(除非任务通常仅超过 500 毫秒 - 可能需要一些调整)。