侦听firebase数据库节点时如何处理kotlin中的竞争条件
Posted
技术标签:
【中文标题】侦听firebase数据库节点时如何处理kotlin中的竞争条件【英文标题】:How to handle race conditions in kotlin, when listening to a firebase database node 【发布时间】:2021-03-20 03:37:23 【问题描述】:今天,当我在我的 android 项目上工作时,我看到了我的应用程序的这种奇怪行为。当我单击一个图标并移动到另一个活动时,这基本上会将另一个孩子插入到第一个活动正在侦听的节点中。但令我惊讶的是,当返回时,屏幕上的所有内容都翻了一番。当我刷新视图时,它会再次变得正常。我重复了很多次,认为这可能是一个小故障。但是每次都是一样的结果。当我查看数据库时,从未创建过新的孩子。
这是我的监听器代码。 (doOnDataChange是一个扩展函数,我是为onValueEventListener定义的)
val chats = mutableListOf<Chat>()
chatsListener = dbChatsReference.orderByChild(NODE_LAST_MESSAGE_TIME).doOnDataChange it ->
chats.clear()
viewLifecycleOwner.lifecycleScope.launch(IO)
// some IO work
for (ss in it.children)
Log.d(TAG, "onDataChange: $ss.key!! , $Thread.currentThread()")
// populate chats with appropriate data
然后我记录了发生的事情。
2020-12-08 23:52:39.721 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: cleared
2020-12-08 23:52:40.043 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: cleared
2020-12-08 23:52:40.132 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: j7ZmBA650WVmvd9z6BsN7UsvPlJ2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.133 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2KQTXik3LjYMriCOdPoTKLNAtoVf2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.139 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2MWUYn8kW4MPlZvnn420XrN4p5kq2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.140 4114-4201/com.skb.skara D/ChatsFragment: onDataChange: j7ZmBA650WVmvd9z6BsN7UsvPlJ2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-3,5,main]
2020-12-08 23:52:40.141 4114-4201/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2KQTXik3LjYMriCOdPoTKLNAtoVf2 , Thread[DefaultDispatcher-worker-3,5,main]
2020-12-08 23:52:40.143 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.145 4114-4201/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2MWUYn8kW4MPlZvnn420XrN4p5kq2 , Thread[DefaultDispatcher-worker-3,5,main]
2020-12-08 23:52:40.146 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2j7ZmBA650WVmvd9z6BsN7UsvPlJ2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.148 4114-4201/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-3,5,main]
2020-12-08 23:52:40.150 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: -MO2_oyUjEV55HAGlzXN , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:40.152 4114-4201/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2j7ZmBA650WVmvd9z6BsN7UsvPlJ2 , Thread[DefaultDispatcher-worker-3,5,main]
2020-12-08 23:52:45.395 4114-4114/com.skb.skara D/ChatsFragment: onCreateView:
2020-12-08 23:52:45.842 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: cleared
2020-12-08 23:52:46.263 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: j7ZmBA650WVmvd9z6BsN7UsvPlJ2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:46.264 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2KQTXik3LjYMriCOdPoTKLNAtoVf2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:46.268 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2MWUYn8kW4MPlZvnn420XrN4p5kq2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:46.273 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2VtDh2KtpeDhwXN386b580Lpr6dj2 , Thread[DefaultDispatcher-worker-2,5,main]
2020-12-08 23:52:46.278 4114-4200/com.skb.skara D/ChatsFragment: onDataChange: lIq43hHgzKfsE0zbYIeiH4dhswT2j7ZmBA650WVmvd9z6BsN7UsvPlJ2 , Thread[DefaultDispatcher-worker-2,5,main]
然后我记得,正如blog 中提到的,当您对数据库进行一些更改时,会立即触发侦听器,而无需真正等待写入完成。之后,如果写入失败,则使用旧值再次触发侦听器以使先前的触发器无效。由于我在这里使用了协程,因此聊天列表由两个线程同时更新。即使对节点进行了两次更改且两者之间的延迟很短,也会发生这种情况。
那么谁能告诉我如何克服这种竞争条件。
【问题讨论】:
【参考方案1】:避免使用可变列表和数据类,而只需对数据使用map
将其转换为列表视图所需类型的只读列表。
让您的 RecyclerView.Adapter 存储一个 var
属性,以便一次更改它。更改数据列表并致电notifyDataSetChanged()
。然后它一次只查看一个列表。
【讨论】:
我应该在这里这样做,以避免竞争条件,还是应该开始在我的项目中到处遵循这个?我的意思是哪个是好的做法或标准做法。 并非在所有情况下都至关重要。部分问题可能是您如何切换调度程序。如果你知道你的可变列表只能被同一个线程修改,你就不必担心其他线程同时操作它。我从不将协程启动到 IO 调度程序中。我只将 IO 代码包装在withContext(IO)
中,并专门在主线程中处理 UI 相关的东西。
哦,是的.. 首先,我必须检查整个项目才能进行您所说的更改。那我稍后再来这里。
实际上,在上面提到的代码中,我在 for 循环中也有 IO 密集型工作。因此,即使我在主线程上编写代码并且只使用 withContextIO 执行特定的 IO 任务,我也会得到相同的结果。唯一改变的是,现在不是两个线程,而是从一个线程内部,两个协程交织在一起,仍然异步更新聊天列表。
要么使用我上面回答的只读列表,要么使用互斥锁来强制一次只有一个线程使用列表和/或属性。以上是关于侦听firebase数据库节点时如何处理kotlin中的竞争条件的主要内容,如果未能解决你的问题,请参考以下文章
当 iOS 应用程序被暂停/杀死并且用户点击通知时如何处理 Firebase 推送通知?
使用 Spring ChannelAwareMessageListener 时如何处理 RabbitMQ 消费者取消通知