所以我在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>>
答案 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 强>
根据docs和sources,传递给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;
}
);
}
我还没有测试:)