Обновление полей значений в 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);