问题描述
我有一个想要清空的 ConcurrentDictionary,将其内容复制到字典(或任何其他容器)中,同时清空它。
一个关键的不变量是在操作过程中没有来自源的元素丢失。
我当前的代码如下所示:
private Dictionary<TKey,TValue> Foo<TKey,TValue>(ConcurrentDictionary<TKey,TValue> source)
{
var result = new Dictionary<TKey,TValue>();
foreach (var item in source)
{
result[item.Key] = item.Value;
}
source.Clear();
return result;
}
根据我的理解,这段代码是线程安全的,但是在 foreach 循环之后和 Clear() 之前并发添加的任何元素都将被清除。
编辑:更精确一些。在我的用例中,该代码是唯一删除某些字典键的代码;其他线程只使用 TryAdd 或 AddOrUpdate。
解决方法
我能想到的唯一方法是TryRemove
所有现有的键,一个一个:
public static Dictionary<TKey,TValue> RemoveAll<TKey,TValue>(
this ConcurrentDictionary<TKey,TValue> source)
{
var result = new Dictionary<TKey,TValue>();
foreach (var key in source.Keys)
{
if (source.TryRemove(key,out var value)) result.Add(key,value);
}
return result;
}
Keys
属性返回 ConcurrentDictionary
中键的快照,因此不可能包含两次相同的键。但它会产生争用,因为它acquires all internal locks。下面是一个使用 ConcurrentDictionary
的惰性枚举器的版本,因此它不会产生太多争用:
public static Dictionary<TKey,TValue>();
foreach (var entry in source)
{
var (key,value) = entry;
if (!result.ContainsKey(key) && source.TryRemove(key,out value))
result.Add(key,value);
}
return result;
}
!result.ContainsKey(key)
的原因是枚举器产生两次相同键的(理论上)可能性。基于 ConcurrentDictionary
、this cannot happen 的当前实现。