如何在.NET ConcurrentDictionary中实现remove_if功能

bma*_*dhu 11 .net c# multithreading task-parallel-library concurrentdictionary

我有一个场景,我必须保持引用计数对象的给定键ConcurrentDictionary,如果引用计数达到0,我想删除键.这必须是线程安全的,因此我打算使用ConcurrentDictionary.

示例程序如下.在并发字典中,我有键和值,值是KeyValuePair,它保存我的自定义对象和引用计数.

ConcurrentDictionary<string, KeyValuePair<object, int>> ccd = 
    new ConcurrentDictionary<string, KeyValuePair<object, int>>();

// following code adds the key, if not exists with reference 
// count   for  my custom object to 1
// if the key already exists it increments the reference count

var addOrUpdateValue = ccd.AddOrUpdate("mykey",
    new KeyValuePair<object, int>(new object(), 1),
    (k, pair) => new KeyValuePair<object, int>(pair.Key, pair.Value + 1));
Run Code Online (Sandbox Code Playgroud)

现在我想要一种方法来在引用计数达到0时删除密钥.我在想,删除带有ConcurrentDictionary键和谓词的方法,如果谓词返回'true'则删除密钥.例.

ConcurrentDictionary.remove(TKey, Predicate<TValue> ). 
Run Code Online (Sandbox Code Playgroud)

没有这样的方法ConcurrentDictionary,问题是如何以线程安全的方式做同样的事情?

Cor*_*son 10

.NET没有RemoveIf直接公开,但它确实暴露了使其工作所必需的构建块而不进行自己的锁定.

ConcurrentDictionaryimplements ICollection<T>,它有一个Remove完整的KeyValuePair而不仅仅是一个键的测试.尽管被隐藏了,但Remove它仍然是线程安全的,我们将用它来实现它.这个工作需要提醒的是,Remove使用EqualityComparer<T>.Default测试值,所以它必须是平等的可比性.你当前的那个不是,所以我们将重新实现它:

struct ObjectCount : IEquatable<ObjectCount>
{
    public object Object { get; }
    public int Count { get; }

    public ObjectCount(object o, int c)
    {
        Object = o;
        Count = c;
    }

    public bool Equals(ObjectCount o) =>
       object.Equals(Object, o.Object) && Count == o.Count;

    public override bool Equals(object o) =>
       (o as ObjectCount?)?.Equals(this) == true;

    // this hash combining will work but you can do better.
    // it is not actually used by any of this code.
    public override int GetHashCode() =>
       (Object?.GetHashCode() ?? 0) ^ Count.GetHashCode();
}
Run Code Online (Sandbox Code Playgroud)

最后,我们将定义一个方法来增加/减少字典中的计数:

void UpdateCounts(ConcurrentDictionary<string, ObjectCount> dict, string key, int toAdd)
{
    var addOrUpdateValue = dict.AddOrUpdate(key,
        new ObjectCount(new object(), 1),
        (k, pair) => new ObjectCount(pair.Key, pair.Value + toAdd));

    if(addOrUpdateValue.Count == 0)
    {
        ((ICollection<KeyValuePair<string, ObjectCount>>)dict).Remove(
            new KeyValuePair<string, ObjectCount>(key, addOrUpdateValue));
    }
}
Run Code Online (Sandbox Code Playgroud)

该键的值可能的通话之间变化AddOrUpdateRemove,但是这不要紧,我们:因为Remove测试的完整KeyValuePair,如果该值以来没有更新改变它只会删除它.

这是设置更改然后使用最终线程安全操作的常见无锁模式,只有在我们的数据结构尚未更新的情况下才能安全地"提交"更改.


Rem*_*anu 5

您不能使用 ConcurrentDictionary,因为它不公开其内部锁定。您的增量必须发生在控制添加的同一锁下(简单的互锁添加是不够的,因为并发线程可能会在您增加计数之前删除该对象)。类似地,递减必须获得锁才能在达到 0 计数时安全地移除它。这个咒语是什么你必须使用一个你明确控制锁定的字典。