Обновление полей значений в ConcurrentDictionary

Я пытаюсь обновить записи в ConcurrentDictionary примерно так:

class Class1
{
    public int Counter { get; set; }
}

class Test
{
    private ConcurrentDictionary<int, Class1> dict =
        new ConcurrentDictionary<int, Class1>();

    public void TestIt()
    {
        foreach (var foo in dict)
        {
            foo.Value.Counter = foo.Value.Counter + 1; // Simplified example
        }
    }
}

По существу мне нужно перебирать словарь и обновлять поле для каждого значения. Из документации я понимаю, что мне нужно избегать использования свойства Value. Вместо этого я думаю, что мне нужно использовать TryUpdate, за исключением того, что я не хочу заменять весь свой объект. Вместо этого я хочу обновить поле объекта.

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

Есть ли у кого-нибудь представление о том, как это сделать?


У меня есть десятки тысяч объектов в словаре, которые мне нужно обновлять каждые тридцать секунд. Создание новых, чтобы обновить свойство, вероятно, невозможно. Мне нужно клонировать существующий объект, обновлять его и заменять в словаре. Id также необходимо заблокировать его на время цикла клонирования/добавления. Тьфу.

Какой идентификатор нравится делать, это перебирать объекты и обновлять свойство Counter напрямую, если это возможно.

Мое последнее исследование привело меня к Parallel.ForEach, который звучит великолепно, но он не должен использоваться для действий, которые обновляют состояние.

Я также упомянул о Interlocked.Increment, который звучит великолепно, но мне все же нужно выяснить, как использовать его для каждого элемента в моем словаре в потоковом безопасном режиме.

Ответ 1

Во-первых, чтобы решить вашу проблему с блокировкой:

class Class1
{
    // this must be a variable so that we can pass it by ref into Interlocked.Increment.
    private int counter;

    public int Counter
    {
        get{return counter; }
    }

    public void Increment()
    {
        // this is about as thread safe as you can get.
        // From MSDN: Increments a specified variable and stores the result, as an atomic operation.
        Interlocked.Increment(ref counter);

        // you can return the result of Increment if you want the new value,
        //but DO NOT set the counter to the result :[i.e. counter = Interlocked.Increment(ref counter);] This will break the atomicity.
    }
}

Итерирование справедливых значений должно быть быстрее, чем повторение пары значений ключа. [Хотя я думаю, что повторение списка ключей и выполнение поисков в большинстве ситуаций будет еще быстрее в ConcurrentDictionary.]

class Test
{
    private ConcurrentDictionary<int, Class1> dictionary = new ConcurrentDictionary<int, Class1>();

    public void TestIt()
    {
        foreach (var foo in dictionary.Values)
        {
            foo.Increment();
        }
    }

    public void TestItParallel()
    {
        Parallel.ForEach(dictionary.Values,x=>x.Increment() );
    }

}

Ответ 2

ConcurrentDictionary не помогает вам одновременно обращаться к членам сохраненных значений, только с самими элементами.

Если несколько потоков вызывают TestIt, вы должны получить снимок коллекции и заблокировать общие ресурсы (которые являются отдельными значениями словаря):

foreach (KeyValuePair<int, Class1> kvp in dict.ToArray())
{
    Class1 value = kvp.Value;
    lock (value)
    {
        value.Counter = value.Counter + 1;
    }
}

Однако, если вы хотите обновить счетчик для определенного ключа, ConcurrentDictionary может помочь вам с атомарным добавлением новой пары значений ключа, если ключ не существует:

Class1 value = dict.GetOrAdd(42, key => new Class1());
lock (value)
{
    value.Counter = value.Counter + 1;
}

AddOrUpdate и TryUpdate действительно предназначены для случаев, когда вы хотите заменить значение для данного ключа в ConcurrentDictionary. Но, как вы сказали, вы не хотите изменять значение, вы хотите изменить свойство значения.

Ответ 3

Вы можете использовать функцию AddOrUpdate.

Вот как вы можете увеличить текущее значение на 1:

dict.AddOrUpdate(key, 1, (key, oldValue) => oldValue + 1);