在ConcurrentDictionary中更新列表

时间:2017-01-11 13:23:50

标签: c# concurrency

所以我在ConcurrentDictionary中有一个IList作为值。

ConcurrentDictionary<int, IList<string>> list1 = new ConcurrentDictionary<int, IList<string>>;

为了更新列表中的值,我这样做:

if (list1.ContainsKey[key])
{
  IList<string> templist;
  list1.TryGetValue(key, out templist);
  templist.Add("helloworld");
}

但是,在 templist 中添加字符串会更新ConcurrentDictionary吗?如果是,那么更新是否是线程安全的,这样就不会发生数据损坏?

或者有更好的方法来更新或创建ConcurrentDictionary中的列表

修改

如果我使用ConcurrentBag而不是List,我该如何实现呢?更具体地说,我该如何更新它? ConcurrentDictionary的TryUpdate方法感觉有些过分。

ConcurrentBag.Add是否在线程安全的mannar中更新ConcurrentDictionary?

ConcurrentDictionary<int, ConcurrentBag<string>> list1 = new ConcurrentDictionary<int, ConcurrentBag<string>>

7 个答案:

答案 0 :(得分:4)

首先,您无需执行ContainsKey() TryGetValue()

你应该这样做:

IList<string> templist;

if (list1.TryGetValue(key, out templist))
    templist.Add("helloworld");

事实上,您编写的代码具有竞争条件。

在一个调用ContainsKey()TryGetValue()的线程之间,一个不同的线程可能已经用该键删除了该项。然后TryGetValue()会将tempList返回为null,然后当您调用tempList.Add()时,您将获得空引用异常。

其次,是的:这里还有另一个可能的线程问题。您不知道字典中存储的IList<string>是线程安全的。

因此,不能保证调用tempList.Add()是安全的。

您可以使用ConcurrentQueue<string>代替IList<string>。这可能是最强大的解决方案。

请注意,仅仅锁定对IList<string>的访问权限是不够的。

这不好:

if (list1.TryGetValue(key, out templist))
{
    lock (locker)
    {
        templist.Add("helloworld");
    }
}

除非您在其他地方使用相同的锁,否则可能会访问IList。这不容易实现,因此最好使用ConcurrentQueue<>或向此类添加锁定并更改体系结构,以便其他线程无法访问底层IList。

答案 1 :(得分:2)

线程安全字典上的操作是按键线程安全的,所以说。因此,只要您从一个线程访问您的值(在本例中为IList<T>),就可以了。

ConcurrentDictionary 阻止两个线程同时访问一个键所属的值。

答案 2 :(得分:2)

已经提到过ConcurrentDictionary与ConcurrentBag的最佳解决方案。只是要添加如何做到

 ConcurrentBag<string> bag= new ConcurrentBag<string>();
 bag.Add("inputstring"); 
 list1.AddOrUpdate(key,bag,(k,v)=>{
     v.Add("inputString");
     return v;
 });

答案 3 :(得分:1)

ConcurrentDictionary对是否可以以线程安全方式对值对象应用更改没有影响。这是值对象的可重用性(IList - 在您的情况下实现)。

查看No ConcurrentList<T> in .Net 4.0?的答案,有一些很好的理由说明.net中没有ConcurrentList实现。

基本上你必须自己处理线程安全的更改。最简单的方法是使用锁定运算符。 E.g。

lock (templist)
{
   templist.Add("hello world");
}

另一种方法是使用.net Framework中的ConcurrentBag。但是,如果您不依赖于IList接口和项目的排序,这种方式仅对您有用。

答案 4 :(得分:0)

您可以使用ConcurrentDictionary.AddOrUpdate方法以线程安全的方式将项目添加到列表中。它更简单,应该可以正常工作。

var list1 = new ConcurrentDictionary<int, IList<string>>();
list1.AddOrUpdate(key, 
      new List<string>() { "test" }, (k, l) => { l.Add("test"); return l;});

<强> UPD

根据docssources,传递给AddOrUpdate方法的工厂将用完锁定范围,因此在工厂委托中调用List方法不是线程安全。

请参阅此答案下的评论。

答案 5 :(得分:0)

  

是否在templist中添加了一个字符串来更新ConcurrentDictionary?

没有。

您的线程安全集合(Dictionary)包含对非线程安全集合(IList)的引用。所以改变它们并不是线程安全的。

我想你应该考虑使用互斥锁。

答案 6 :(得分:0)

如果您使用ConcurrentBag<T>

var dic = new ConcurrentDictionary<int, ConcurrentBag<string>>();

类似的方法可以正常工作:

public static class ConcurentDictionaryExt
{
    public static ConcurrentBag<V> AddToInternal<K, V>(this ConcurrentDictionary<K, ConcurrentBag<V>> dic, K key, V value)
        => dic.AddOrUpdate(key,
            k => new ConcurrentBag<V>() { value },
            (k, existingBag) =>
                {
                    existingBag.Add(value);
                    return existingBag;
                }
            );

    public static ConcurrentBag<V> AddRangeToInternal<K, V>(this ConcurrentDictionary<K, ConcurrentBag<V>> dic, K key, IEnumerable<V> values)
        => dic.AddOrUpdate(key,
            k => new ConcurrentBag<V>(values),
            (k, existingBag) =>
                {
                    foreach (var v in values)
                        existingBag.Add(v);
                    return existingBag;
                }
            );
}

我还没有测试:)