收听 firebase 数据库节点时如何处理 kotlin 中的竞争条件
How to handle race conditions in kotlin, when listening to a firebase database node
今天,当我在处理我的 android 项目时,我看到我的应用程序有这种奇怪的行为。当我单击一个图标并移动到另一个 activity 时,这基本上会将另一个 child 插入到第一个 activity 正在侦听的节点中。但令我惊讶的是,当 return 回来时,屏幕上的所有内容都翻了一番。当我刷新视图时,它又会恢复正常。我重复了很多次,认为这可能是一个小故障。但是每次都是一样的结果。当我查看数据库时,从未创建新的 child。
这是我的监听器代码。 (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中提到的,当你对数据库进行一些更改时,会立即触发监听器,而不需要等待写入完成。之后,如果写入失败,则使用旧值再次触发侦听器以使先前的触发无效。由于我在这里使用协程,聊天列表由两个线程同时更新。即使对节点进行两次更改且中间的延迟非常短,也会发生这种情况。
谁能告诉我如何克服这种竞争条件。
避免使用可变列表和数据 类,而只是对数据使用 map
将其转换为列表视图所需类型的只读列表。
让您的 RecyclerView.Adapter 存储一个 var
属性 以便一次性全部更改。更改数据列表并调用notifyDataSetChanged()
。然后它一次只查看一个列表。
今天,当我在处理我的 android 项目时,我看到我的应用程序有这种奇怪的行为。当我单击一个图标并移动到另一个 activity 时,这基本上会将另一个 child 插入到第一个 activity 正在侦听的节点中。但令我惊讶的是,当 return 回来时,屏幕上的所有内容都翻了一番。当我刷新视图时,它又会恢复正常。我重复了很多次,认为这可能是一个小故障。但是每次都是一样的结果。当我查看数据库时,从未创建新的 child。
这是我的监听器代码。 (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中提到的,当你对数据库进行一些更改时,会立即触发监听器,而不需要等待写入完成。之后,如果写入失败,则使用旧值再次触发侦听器以使先前的触发无效。由于我在这里使用协程,聊天列表由两个线程同时更新。即使对节点进行两次更改且中间的延迟非常短,也会发生这种情况。
谁能告诉我如何克服这种竞争条件。
避免使用可变列表和数据 类,而只是对数据使用 map
将其转换为列表视图所需类型的只读列表。
让您的 RecyclerView.Adapter 存储一个 var
属性 以便一次性全部更改。更改数据列表并调用notifyDataSetChanged()
。然后它一次只查看一个列表。