ViewModel 具有一些长时间执行的函数,并且 UI 中的单个微调器适用于所有这些函数

问题描述 投票:0回答:1

我的视图模型有几个函数可以运行启动协程的长时间后台操作。在用户界面中,我有一个微调器,至少在此类后台进程处于活动状态时应显示该微调器。实现这种旋转器的最佳方法是什么?

我通过使用计数器和 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) }
    }
}
kotlin viewmodel kotlin-coroutines
1个回答
0
投票

由于嵌套协程的启动,可能会出现竞争。启动

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 毫秒 - 可能需要一些调整)。

© www.soinside.com 2019 - 2024. All rights reserved.