我有ConcurrentDictionary
stoing Item
s:
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
不一定是原子的。
所以,我担心以下竞争条件:
item = items[itemId]
,但尚未执行lock(item)
lock(item = items[itemId])
itemId
itemId
items
lock(item)
,不知道itemId
已不在词典中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
是游戏的当前状态。该词典持有正在进行的项目。操作是处理玩家的移动,例如“来自e3的骑士进入d1”。如果由于玩家移动游戏完成,那么我们return the final state of the game并从字典中移除游戏。当然,对完成的游戏进行任何进一步的移动是无效的,因此是try / catch块。
try / catch块应该能够正确检测以下情况:
答案 0 :(得分:0)
您的更新代码并不是更好。一个线程仍然可以从字典中获取值,而不是锁定,然后让另一个线程在锁定被取出之前删除该项目。
从根本上说,你希望代码是原子的,而不仅仅是对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;
}
}
}
}