【问题标题】:Updating fields of values in a ConcurrentDictionary更新 ConcurrentDictionary 中的值字段
【发布时间】:2010-05-25 00:38:16
【问题描述】:

我正在尝试更新 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 团队博客上的 this blog entry 之后:也许我需要使用 AddOrUpdate 并且在添加委托中什么都不做。

有没有人知道如何做到这一点?


我的字典中有数以万计的对象,我需要每三十秒左右更新一次。创建新的以更新属性可能是不可行的。我需要克隆现有对象,更新它并替换字典中的那个。我还需要在克隆/添加周期期间锁定它。呸。

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

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

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

【问题讨论】:

  • 如果您仅每 30 秒更新一次每个值的字段,实际上是否存在并发?听起来像一个普通的字典和一个 foreach 循环应该是你所需要的。 Parallel.ForEach 也应该可以工作,因为没有共享资源。
  • 当更新线程运行时,额外的对象将从其他线程的字典中添加和删除。此外,对象上的其他属性将通过其他线程更新以响应 WCF 消息。所以是的,有很多并发性。感谢您的帮助!
  • 好的。然后我的答案仍然存在。使用 AddOrUpdate/GetOrAdd/TryUpdate 等操作字典的内容;迭代时对字典进行快照;更改属性时锁定各个 Class1 实例。

标签: c# concurrency .net-4.0


【解决方案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);
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    相关资源
    最近更新 更多