【问题标题】:How to restart a function, when the data it operating on it is updated如何重新启动函数,当它操作的数据更新时
【发布时间】:2020-11-28 17:00:53
【问题描述】:

在片段的onCreateView() 方法中,我将3 个valueEventListeners 附加到firebase 数据库中不同的、互斥的位置。每当引用处的数据更新时,它们都会异步触发。我的示例代码如下所示:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    listner1 = ref1.addCustomValueEventListener {
        list1.clear()
        // do some operation and populate list1
        rewriteMainData()
    }
    listner2 = ref2.addCustomValueEventListener {
        list2.clear()
        // do some operation and populate list2
        rewriteMainData()
    }
    listner3 = ref3.addCustomValueEventListener {
        list3.clear()
        // do some operation and populate list3
        rewriteMainData()
    }
}

private fun rewriteMainData() {
    mainList.clear()
    mainDbReference.addCustomListenerForSingleValueEvent {
        // compare the values present here with values in list1, list2, list3
        // populate mainList with appropriate values
        // notifyAdapterDataSetChanged
    }
    return
}

// EXTENSION FUNCTION
internal inline fun DatabaseReference.addCustomValueEventListener(crossinline onDataChange: (DataSnapshot) -> Unit) =
addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        onDataChange(snapshot)
    }
    override fun onCancelled(error: DatabaseError) {
        Log.e(TAG, "onCancelled: ", error.toException())
    }
})

然而,在这个例子中,如果一个监听器紧接着另一个被触发,那么rewriteMainData() 将被调用两次。届时,mainList 将被两个回调同时写入。解决此问题的一种方法是使用ReentrantLock 的实例来避免同时写入。但这使得最新的调用要等到之前的调用完成,这是多余的,因为所有数据都将被清除和重写。
因此,有什么方法可以知道何时调用 rewriteMainData() 以及它是否已经在运行,那么只需中止并使用更新的列表重新启动函数?

【问题讨论】:

    标签: kotlin firebase-realtime-database callback kotlin-coroutines


    【解决方案1】:

    您可以yield,每次在做任何密集工作之前检查当前协程的取消。

    // instance variable on the class
    var job1: Job? = null
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        ref1.addCustomValueEventListener {
            job1?.cancel()
            job2?.cancel()
            job3?.cancel()
    
            job1 = lifecycleScope.launch(Dispatchers.Default) {
                list1.clear()
        
                // do some operation and populate list1
                rewriteMainData()
            }
        }
        // ...
    }
    
    
    private suspend fun rewriteMainData() {
        mainList.clear()
    
        // before every intensive work, to check for cancellation.
        yield()
        // some work involving cpu consumption or takes significant time.
    }
    

    请注意,如果工作只是同步更新列表,则影响很小甚至更糟,这仅涉及几个 CPU 周期和毫秒的时间。仅当您确定 rewriteMainData 在您当前的代码中所做的工作会影响性能时才使用此选项。

    【讨论】:

    • 现在,我所拥有的只是在另一个数据库引用上调用单个值事件,并将这些值与 list1、2、3 进行比较,并将适当的值写入 mainList,然后通知AdapterDataSetChanged。正如你所提到的,我认为让它保持原样是件好事。但是感谢这个答案,将来可能会得到很大的帮助。
    【解决方案2】:

    mainList 将被两个回调同时写入。

    使用您这里的代码是不可能的。数据库回调默认在 Android 主线程上调用,这意味着它们不能同时被调用。它们将被顺序调用。因此,您不必担心线程或锁定。

    【讨论】:

    • 如果我理解正确的话,如果新事件已经到来并且旧功能仍在执行(考虑到它有些密集且不相关的工作),他实际上想拆除对旧事件的响应所做的任何工作。 IO 可能在不同的线程中完成,然后发布到 Main 以进行调用,但另一个 IO 线程可能正在等待事件被消耗。
    • @AnimeshSahu 我在代码中看不到任何 I/O 或任何异步内容。它都是同步的,除了数据库监听器,它将在主线程上按顺序调用。
    • 如果它被调用,那很好。但是,如果在运行 rewriteMainData() 函数的过程中更新了数据库值,那么继续使用该函数中的旧数据是没有用的。那么有没有办法中止当前运行,并使用更新的值重新运行?
    • 我不确定你在问什么。侦听器是反应式的,并且会随着时间的推移反映数据库的当前状态。锁定事物根本不会改变或帮助这种行为 - 锁定主线程只会造成伤害。如果这里的某些东西没有按您期望的方式工作,那么请提出一个新问题来解释它是什么。
    • @DougStevenson rewriteMainData() 涉及在另一个数据库引用上调用单个值事件,并将这些值与 list1、2、3 进行比较,并将适当的值写入 mainList,然后通知AdapterDataSetChanged。
    猜你喜欢
    • 1970-01-01
    • 2019-10-27
    • 2016-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多