c# - 如何锁定 ConcurrentDictionary 的元素?
问题描述
我有一个ConcurrentDictionary
stoing Item
:
ConcurrentDictionary<ulong, Item> items;
现在我想锁定Item
这本字典中的一个,这样我就可以安全地操作它了。
这段代码正确吗?
try
{
Item item;
lock(item = items[itemId])
{
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
我的恐惧是这样的。我想lock(item = items[itemId])
可以分解为两个操作:
- 分配参考
items[itemId]
到item
- 锁上
item
这不一定是原子的。
所以,我担心以下竞争条件:
- 线程 1 执行
item = items[itemId]
,但尚未执行lock(item)
- 线程 2 执行
lock(item = items[itemId])
相同的值itemId
- 线程 2
itemId
从items
- 线程 2 释放它的锁
- 线程1执行
lock(item)
,不知道itemId
不再在字典中 - 线程 1 错误地在 上运行
item
,而不是像它应该的那样去它的catch
块。
上面的分析正确吗?
在那种情况下,以这种方式修改我的代码就足够了吗?
try
{
Item item;
lock(items[itemId])
{
item = items[itemId];
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
编辑:因为我开始假设我已经陷入了 XY 问题。这是背景。
多人国际象棋游戏。itemId
是游戏的ID。item
是游戏的当前状态。dict 包含正在进行的项目。该操作是处理玩家的移动,例如“来自e3的骑士到d1”。如果由于玩家的移动游戏完成,那么我们返回游戏的最终状态并将游戏从字典中删除。当然,在已完成的游戏上执行任何进一步的移动都是无效的,因此使用 try/catch 块。
try/catch 块应该正确检测以下情况:
- 玩家发送无效命令,命令在不存在的游戏中移动;
- 由于网络延迟,玩家在有效游戏中进行移动的命令会在他们的计时器用完后到达服务器。
解决方案
您更新的代码也好不到哪里去。一个线程仍然有可能从字典中获取值,而不是锁定,然后让另一个线程在锁定被取出之前删除该项目。
从根本上说,您希望代码是原子的,而不仅仅是对您的单次调用ConcurrentDictionary
,因此您只需要进行自己的锁定并使用常规字典。
这里的主要问题源于您尝试锁定可能存在或不存在或可能正在改变的对象。那只会给你带来各种各样的问题。一种替代解决方案是不这样做,并确保字典不会更改或删除键的值,以便您可以安全地锁定它。一种方法是创建一个包装器对象:
public static void Foo(ConcurrentDictionary<ulong, ItemWrapper> items, ulong itemId)
{
if (!items.TryGetValue(itemId, out ItemWrapper wrapper))
{
//no item
}
lock (wrapper)
{
if (wrapper.Item == null)
{
//no actual item
}
else
{
if (ShouldRemoveItem(wrapper.Item))
{
wrapper.Item = null;
}
}
}
}
推荐阅读
- json - vscode json文件
- ruby-on-rails - 文件中的葡萄 Swagger 描述
- apache-kafka - 如何使两个DC之间的kafka集群中的生产者幂等?
- javascript - 提交表单并验证 TOKEN JWT 后,再次加载 Vue JS 实例
- c# - 查看新用户加入电报频道时使用的链接
- laravel - Laravel可邮寄,实现ShouldQueue时数据不会传递给Blade
- d3.js - 单击按钮切换数据集时无法更新图表
- spring - SourcePollingChannelAdapter:应用程序启动后可以任意延迟启动轮询触发器吗?
- python - 为什么在单行中使用条件时我不能在 python if else 语句中使用 pass
- javascript - React - 传单删除标记