线程安全的集合所在的命名空间 using System.Collections.Concurrent;
Concurrent意思是并发的,并行的。反义是sequential(顺序的),线程安全的意思就是多线程中的同步锁(Lock)
ConcurrentDictionary<TKey,TValue> 类
表示可由多个线程同时访问的键/值对的线程安全集合。
参考微软官方文档:
https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.concurrent?view=netframework-4.5
参考源代码(类BlockingCollection<T>)
与 System.Collections.Generic.Dictionary<TKey,TValue> 类一样, ConcurrentDictionary<TKey,TValue> 实现 IDictionary<TKey,TValue> 接口。 此外, ConcurrentDictionary<TKey,TValue> 提供了几种在字典中添加或更新键/值对的方法,如下表所述。
任务 | 方法 | 使用注意事项 |
---|---|---|
如果字典中不存在新项,则将其添加到字典中 | TryAdd | 如果字典中当前不存在该键,则此方法将添加指定的键/值对。 方法返回 true 或, false 具体取决于是否已添加新对。 |
更新字典中现有键的值(如果该注册表项具有特定值) | TryUpdate | 此方法检查键是否具有指定的值,如果它存在,则使用新值更新该键。 它与 CompareExchange 方法类似,只是它用于字典元素。 |
无条件地将键/值对存储在字典中,并覆盖已存在的键的值 | 索引器的 setter: dictionary[key] = newValue | |
将键/值对添加到字典中,或者如果该键已存在,则根据键的现有值更新该键的值 | AddOrUpdate(TKey, Func<TKey,TValue>, Func<TKey,TValue,TValue>) - 或 - AddOrUpdate(TKey, TValue, Func<TKey,TValue,TValue>) | AddOrUpdate(TKey, Func<TKey,TValue>, Func<TKey,TValue,TValue>) 接受密钥和两个委托。 如果字典中不存在该键,则使用第一个委托;它接受键并返回应为该项添加的值。 如果该键存在,它将使用第二个委托;它接受键及其当前值,并返回应为密钥设置的新值。 AddOrUpdate(TKey, TValue, Func<TKey,TValue,TValue>) 接受键、要添加的值和更新委托。 这与上一个重载相同,不同之处在于它不使用委托来添加密钥。 |
获取字典中的键的值,将值添加到字典中,如果键不存在,则返回它 | GetOrAdd(TKey, TValue) - 或 - GetOrAdd(TKey, Func<TKey,TValue>) | 这些重载为字典中的键/值对提供延迟初始化,仅在不存在时才添加值。 GetOrAdd(TKey, TValue) 如果键不存在,则采用要添加的值。 GetOrAdd(TKey, Func<TKey,TValue>) 如果键不存在,则使用将生成值的委托。 |
所有这些操作都是原子操作,对类的所有其他操作都是线程安全的 ConcurrentDictionary<TKey,TValue> 。 唯一的例外是接受委托的方法,即 AddOrUpdate 和 GetOrAdd 。 对于字典的修改和写入操作, ConcurrentDictionary<TKey,TValue> 使用细粒度锁定以确保线程安全。 对字典进行 (读取操作时,将以无锁方式执行。 ) 不过,这些方法的委托在锁的外部调用,以避免在锁定下执行未知代码导致的问题。 因此,这些委托执行的代码不服从操作的原子性。
测试程序:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadSafeCollectionDemo
{
class Program
{
static void Main(string[] args)
{
int initialCapacity = 16;
int concurrencyLevel = Environment.ProcessorCount * 2;
ConcurrentDictionary<int, string> cd = new ConcurrentDictionary<int, string>(concurrencyLevel, initialCapacity);
Console.WriteLine($"当前字典元素个数:【{cd.Count}】,是否为空【{cd.IsEmpty}】");
Task taskAdd = Task.Factory.StartNew(() =>
{
bool result = cd.TryAdd(1, "北洛");
Console.WriteLine($"添加键【1】-值【北洛】的结果:【{result}】");
result = cd.TryAdd(3, "玄戈");
Console.WriteLine($"添加键【3】-值【玄戈】的结果:【{result}】");
result = cd.TryAdd(1, "巫炤");
Console.WriteLine($"添加键【1】-值【巫炤】的结果:【{result}】");
});
Task taskUpdate = Task.Run(() =>
{
cd.AddOrUpdate(4, "云无月", (key, val) => "嫘祖");
Console.WriteLine($"key:【4】,value:【{cd[4]}】");
cd.AddOrUpdate(4, "云无月", (key, val) => "嫘祖");
Console.WriteLine($"key:【4】,value:【{cd[4]}】");
for (int i = 2; i <= initialCapacity + 1; i++)
{
string val = "古剑" + i;
bool result = cd.TryAdd(i, val);
Console.WriteLine($"添加键【{i}】-值【{val}】的操作结果【{result}】");
}
});
Console.WriteLine($"获取或者添加键【28】{cd.GetOrAdd(28, "川大肥")}");
Task.WaitAll(taskAdd, taskUpdate);
Console.WriteLine($"获取或者添加键【10】,如果键存在则获取。{cd.GetOrAdd(10, "更新")}");
Console.WriteLine();
Console.WriteLine("......现在测试同时移除元素功能......");
Task taskRemove1 = Task.Run(() =>
{
string val;
bool result;
foreach (int key in cd.Keys)
{
result = cd.TryRemove(key, out val);
Console.WriteLine($"线程【{Thread.CurrentThread.ManagedThreadId}】:尝试移除键【{key}】-值【{val}】,移除结果【{result}】");
}
});
Task taskRemove2 = Task.Run(() =>
{
string val;
bool result;
foreach (int key in cd.Keys)
{
result = cd.TryRemove(key, out val);
Console.WriteLine($"线程【{Thread.CurrentThread.ManagedThreadId}】:尝试移除键【{key}】-值【{val}】,移除结果【{result}】");
}
});
Task.WhenAll(taskRemove1, taskRemove2);
Console.ReadLine();
}
}
}