Как я могу заблокировать элемент ConcurrentDictionary? - PullRequest
0 голосов
/ 24 мая 2018

У меня есть 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]) можно разложить на две операции:

  1. Назначить ссылку items[itemId] на item
  2. Блокировка на item

Не обязательно атомарные.

Итак, я боюсь следующего состояния гонки:

  1. Поток 1 выполняет item = items[itemId], но еще не выполняет lock(item)
  2. Поток 2 выполняет lock(item = items[itemId]) для того же значения itemId
  3. Поток 2 стирает itemId с items
  4. Поток 2 снимает свою блокировку
  5. Поток 1 выполняет lock(item), не зная, что itemId больше не находится в словаре
  6. Поток 1 неправильно работает с item, вместо того, чтобы идти к его блоку catch, как и должно быть.

Является ли приведенный выше анализ правильным?

В таком случае, будет ли достаточно изменить мой код таким образом?

try
{
    Item item;
    lock(items[itemId])
    {
        item = items[itemId];
        // do stuff
        // possibly remove itemId from the dictionary
    }
}
catch(KeyNotFoundException)
{
    // ...
}

РЕДАКТИРОВАТЬ: потому что яначинаю предполагать, что я влюбился в проблему XY.Вот фон.

Многопользовательская игра в шахматы.itemId - это идентификатор игры.item - текущее состояние игры.Дикт содержит текущие предметы.Операция состоит в том, чтобы обработать ход игрока, как «рыцарь с e3 идет на d1».Если из-за хода игрока игра заканчивается, то мы возвращаем конечное состояние игры и удаляем игру из словаря.Разумеется, недопустимо совершать любые дальнейшие ходы в завершенной игре, отсюда и блок try / catch.

Блок try / catch должен правильно определять следующие ситуации:

  • игрок посылает недопустимую команду, приказывая сделать ход в несуществующей игре;
  • Из-за задержки в сети, команда игроков, чтобы сделать ход в действительной игре, поступает на сервер после того, кактаймер истек.

1 Ответ

0 голосов
/ 24 мая 2018

Ваш обновленный код не лучше.Поток по-прежнему может получить значение из словаря, но не заблокировать его, а затем заставить другой поток удалить элемент до снятия блокировки.

По сути, вы хотите, чтобы код был атомарным, а не другимчем просто один вызов вашего 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;
            }
        }
    }
}
...