I have a ConcurrentDictionary
stoing Item
s:
ConcurrentDictionary<ulong, Item> items;
Now I would like to lock on an Item
from this dictionary so I may safely operate on it.
Is this code correct?
try
{
Item item;
lock(item = items[itemId])
{
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
My fear is this. I suppose lock(item = items[itemId])
can be decomposed into two operations:
- Assign reference of
items[itemId]
toitem
- Lock on
item
Which are not necessarily atomic.
So, I'm fearing the following race condition:
- Thread 1 performs
item = items[itemId]
, but doesn't yet performlock(item)
- Thread 2 performs
lock(item = items[itemId])
for the same value ofitemId
- Thread 2 erases
itemId
fromitems
- Thread 2 releases its lock
- Thread 1 performs
lock(item)
, not knowingitemId
is no longer in the dictionary - Thread 1 incorrectly operates on
item
, instead of going to itscatch
block, as it should.
Is the above analysis correct?
In that case, would it be sufficient to modify my code this way?
try
{
Item item;
lock(items[itemId])
{
item = items[itemId];
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
EDIT: Because I'm starting to suppose I've fallen for the XY problem. Here's the background.
Multiplayer chess game. itemId
is the game's ID. item
is the game's current state. The dict holds ongoing items. The operation is to process a player's move, like "knight from e3 goes to d1". If because of a player's move the game completes, then we return the final state of the game and remove the game from the dictionary. It is, of course, invalid to perform any further moves on a completed game, hence the try/catch block.
The try/catch block is supposed to correctly detect the following situations:
- a player sends an invalid command, ordering to make a move in a non-existent game;
- Because of network lag, a players command to make a move on a valid game arrives to the server after their timer run out.