更新ConcurrentDictionary中的值字段

时间:2010-05-25 00:38:16

标签: c# concurrency .net-4.0

我正在尝试更新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上的字段。我从文档中了解到我需要避免使用Value属性。相反,我认为我需要使用TryUpdate,除了我不想替换我的整个对象。相反,我想更新对象上的字段。

在PFX团队博客上阅读this blog entry之后:也许我需要使用AddOrUpdate并且在添加委托中什么都不做。

有没有人对如何做到这一点有任何见解?


我在字典中有成千上万的对象,我需要每隔30秒左右更新一次。创建新的以更新属性可能是不可行的。我需要克隆现有对象,更新它并替换字典中的对象。我还需要在克隆/添加周期的持续时间内锁定它。呸。

我想做的是迭代对象并尽可能直接更新Counter属性。

我的最新研究让我看到了Parallel.ForEach,听起来不错,但它不应该用于更新状态的动作。

我也看到了Interlocked.Increment的提及,这听起来不错,但我仍然需要弄清楚如何以线程安全的方式在我的字典中的每个元素上使用它。

3 个答案:

答案 0 :(得分:12)

首先,要解决锁定问题:

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() );
    }

}

答案 1 :(得分:3)

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中替换给定键的值的情况。但是,正如您所说,您不想更改值,您想要更改值的属性。

答案 2 :(得分:1)

您可以使用AddOrUpdate函数。

以下是将当前值增加1的方法:

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