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

时间:2016-09-24 18:44:17

标签: c# .net 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));

现在我想要一种在引用计数达到0时删除密钥的方法。我在想,删除ConcurrentDictionary上带有键和谓词的方法,如果谓词返回&#39;为真,则删除键。 #39 ;.实施例。

ConcurrentDictionary.remove(TKey, Predicate<TValue> ). 

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

4 个答案:

答案 0 :(得分:10)

.NET不直接公开SELECT t1.BakeTime AS BakeTime1, t2.BakeTime as BakeTime2, t1.Diameter AS Diameter1, t2.Diameter AS Diameter2, t1.Size AS Size1, t2.Size AS Size2, t1.Height AS Height1, t2.Height AS Height2 FROM YourTable t1 INNER JOIN YourTable t2 ON t2.UniqueId = t1.UniqueId AND t2.CakeNo != t1.CakeNo WHERE t1.UniqueId = 'YourIdHere' AND RIGHT(t1.CakeNo, 1) = 'A' ,但它确实暴露了使其工作所必需的构建块而不进行自己的锁定。

RemoveIf实现了ConcurrentDictionary,其中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();
}

该密钥的值可能会在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)); } } AddOrUpdate的调用之间更改,但这对我们无关紧要:因为Remove会测试完整的Remove ,只有在更新后值没有改变时才会删除它。

这是一种常见的无锁模式,即设置更改,然后使用最终的线程安全操作来安全地“提交”更改,只有在我们的数据结构尚未同时更新的情况下。

答案 1 :(得分:3)

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

答案 2 :(得分:0)

这将为您提供一个字典,用于跟踪项目的计数(如果项目不为零)并且在项目为0时没有项目。增量和减量非常简单。删除空节点看起来很奇怪,但即使添加和删除不按顺序,也会保留准确的计数。递减初始值-1,再次是在调用无序时处理。

有时并发编程很奇怪。

   private void Increment(string key)
    {
       var result =  ccd.AddOrUpdate(key,new KeyValuePair<object, int>(new object(), 1),(k, pair) => new KeyValuePair<object, int>(pair.Key, pair.Value + 1));
       RemoveEmptyNode(key, result);

    }

    private void Decrement(string key)
    {
        var result = ccd.AddOrUpdate(key, new KeyValuePair<object, int>(new object(), -1), (k, pair) => new KeyValuePair<object, int>(pair.Key, pair.Value - 1));
        RemoveEmptyNode(key, result);
    }

    private void RemoveEmptyNode(string key, KeyValuePair<object, int> result)
    {
        if (result.Value == 0)
        {
            KeyValuePair<object, int> removedKeyValuePair;
            if (ccd.TryRemove(key, out removedKeyValuePair))
            {
                if (removedKeyValuePair.Value != 0)
                {
                    ccd.AddOrUpdate(key, removedKeyValuePair,
                        (k, pair) => new KeyValuePair<object, int>(key, pair.Value + removedKeyValuePair.Value));
                }
            }
        }
    }
}

答案 3 :(得分:0)

我有一个类似的问题 - 拥有多线程代码,我需要计算我访问某种类型资源的次数。换句话说 - 我需要找到对不同资源类型的访问权限。

我解决它的方式:

为您的计数创建商店:

ConcurrentDictionary<string, StrongBox<int>> _counts = new ConcurrentDictionary<string, StrongBox<int>>();

访问资源时,增加访问次数:

Interlocked.Increment(ref _counts.GetOrAdd(_resourceType, new StrongBox<int>(0)).Value);

在你的情况下,你也必须照顾减量。

我知道这不是你提出的问题的完整解决方案,并不是它的直接答案,但我希望它对某人有用。

相关问题