简体   繁体   English

线程安全收集与上限

[英]thread safe Collection with upper bound

I am after a collection with the following properties: 我在具有以下属性的集合之后:

  • threadsafe : it will be used in asp.net and multiple clients could try to add, remove and access members concurrently threadsafe :它将在asp.net中使用,多个客户端可以尝试同时添加,删除和访问成员
  • max elements : I want to be able to set an upper bound, a maximum number of elements, at construction time max elements :我希望能够在构造时设置上限,最大元素数
  • TryAdd : a method that works the same as BlockingCollection<T>.TryAdd(T) would be perfect, ie it would return false if the maximum number of elements has been reached TryAdd :与BlockingCollection<T>.TryAdd(T)相同的方法将是完美的,即如果已达到最大元素数,它将返回false
  • Dictionary-like : In most other respects a ConcurrentDictionary would be perfect, ie ability to identify elements by a key, remove any item (not just the first or last, which I think would be the limitation with BlockingCollection ) 类似于字典 :在大多数其他方面, ConcurrentDictionary是完美的,即通过键识别元素的能力,删除任何项目(不仅仅是第一个或最后一个,我认为这将是BlockingCollection的限制)

Before I attempt to roll my own, my questions are: 在我试图推销自己之前,我的问题是:

  1. have I missed a built in type that would put a safe ceiling on the number of elements in a collection? 我错过了一个内置类型,它会对集合中的元素数量设置安全上限吗?
  2. Is there a way to achieve this functionality with BlockingCollection somehow? 是否有办法以某种方式使用BlockingCollection实现此功能?

Finally, if I do need to try and make my own, what approach should I think about? 最后,如果我确实需要尝试自己做,我应该考虑什么方法? Is it as simple as a wrapped Dictionary with locks ? 它是否像带有locks的包装Dictionary一样简单?

Example use: A chat room with a defined limit on number of participants could store the connection information of participants and reject new entrants until there is room to enter when full 使用示例:具有参与人数限定限制的聊天室可以存储参与者的连接信息并拒绝新参赛者,直到有足够的空间进入

Here is a simple implementation for this: 这是一个简单的实现:

public class ConcurrentDictionaryEx<TKey, TValue>
{
    private readonly object _lock = new object();
    private ConcurrentDictionary<TKey, TValue> _dic;
    public int Capacity { get; set; }
    public int Count { get; set; }
    public ConcurrentDictionaryEx(int capacity, int concurrencyLevel = 2)
    {
        this.Capacity = capacity;
        _dic = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, capacity);
    }

    public bool TryAdd(TKey key, TValue value)
    {
        lock (_lock)
        {
            if (this.Count < this.Capacity && _dic.TryAdd(key, value))
            {
                this.Count++;
                return true;
            }
            return false;

        }
    }

    public bool TryRemove(TKey key, out TValue value)
    {
        lock (_lock)
        {
            if (_dic.TryRemove(key, out value))
            {
                this.Count--;
                return true;
            }
            return false;
        }
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        lock (_lock)
        {
            return _dic.TryGetValue(key, out value);
        }
    }

    public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue)
    {
        lock (_lock)
        {
            return _dic.TryUpdate(key, newValue, comparisonValue);
        }
    }
}

The simplest solution is just make a wrapper class that uses a normal dictionary and uses a ReaderWriterLockSlim to control thread safe access. 最简单的解决方案是创建一个使用普通字典的包装类,并使用ReaderWriterLockSlim来控制线程安全访问。

public class SizeLimitedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private readonly int _maxSize;
    private readonly IDictionary<TKey, TValue> _dictionary;
    private readonly ReaderWriterLockSlim _readerWriterLock;

    public SizeLimitedDictionary(int maxSize)
    {
        _maxSize = maxSize;
        _dictionary = new Dictionary<TKey, TValue>(_maxSize);
        _readerWriterLock = new ReaderWriterLockSlim();
    }

    public bool TryAdd(TKey key, TValue value)
    {
        _readerWriterLock.EnterWriteLock();
        try
        {
            if (_dictionary.Count >= _maxSize)
                return false;

            _dictionary.Add(key, value);
        }
        finally
        {
            _readerWriterLock.ExitWriteLock();
        }

        return true;
    }

    public void Add(TKey key, TValue value)
    {
        bool added = TryAdd(key, value);
        if(!added)
            throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
    }

    public bool TryAdd(KeyValuePair<TKey, TValue> item)
    {
        _readerWriterLock.EnterWriteLock();
        try
        {
            if (_dictionary.Count >= _maxSize)
                return false;

            _dictionary.Add(item);
        }
        finally
        {
            _readerWriterLock.ExitWriteLock();
        }

        return true;
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        bool added = TryAdd(item);
        if (!added)
            throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
    }

    public void Clear()
    {
        _readerWriterLock.EnterWriteLock();
        try
        {
            _dictionary.Clear();
        }
        finally
        {
            _readerWriterLock.ExitWriteLock();
        }

    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        _readerWriterLock.EnterReadLock();
        try
        {
            return _dictionary.Contains(item);
        }
        finally
        {
            _readerWriterLock.ExitReadLock();
        }

    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _readerWriterLock.EnterReadLock();
        try
        {
            _dictionary.CopyTo(array, arrayIndex);
        }
        finally
        {
            _readerWriterLock.ExitReadLock();
        }
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        _readerWriterLock.EnterWriteLock();
        try
        {
            return _dictionary.Remove(item);
        }
        finally
        {
            _readerWriterLock.ExitWriteLock();
        }
    }

    public int Count
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            try
            {
                return _dictionary.Count;
            }
            finally
            {
                _readerWriterLock.ExitReadLock();
            }
        }
    }

    public bool IsReadOnly
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            try
            {
                return _dictionary.IsReadOnly;
            }
            finally
            {
                _readerWriterLock.ExitReadLock();
            }
        }
    }

    public bool ContainsKey(TKey key)
    {
        _readerWriterLock.EnterReadLock();
        try
        {
            return _dictionary.ContainsKey(key);
        }
        finally
        {
            _readerWriterLock.ExitReadLock();
        }
    }

    public bool Remove(TKey key)
    {
        _readerWriterLock.EnterWriteLock();
        try
        {
            return _dictionary.Remove(key);
        }
        finally
        {
            _readerWriterLock.ExitWriteLock();
        }
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        _readerWriterLock.EnterReadLock();
        try
        {
            return _dictionary.TryGetValue(key, out value);
        }
        finally
        {
            _readerWriterLock.ExitReadLock();
        }
    }

    public TValue this[TKey key]
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            try
            {
                return _dictionary[key];
            }
            finally
            {
                _readerWriterLock.ExitReadLock();
            }
        }
        set
        {
            _readerWriterLock.EnterUpgradeableReadLock();
            try
            {
                var containsKey = _dictionary.ContainsKey(key);
                _readerWriterLock.EnterWriteLock();
                try
                {
                    if (containsKey)
                    {
                        _dictionary[key] = value;
                    }
                    else
                    {
                        var added = TryAdd(key, value);
                        if(!added)
                            throw new InvalidOperationException("Dictionary is at max size, can not add additional members.");
                    }
                }
                finally
                {
                    _readerWriterLock.ExitWriteLock();
                }
            }
            finally
            {
                _readerWriterLock.ExitUpgradeableReadLock();
            }
        }
    }

    public ICollection<TKey> Keys
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            try
            {
                return _dictionary.Keys;
            }
            finally
            {
                _readerWriterLock.ExitReadLock();
            }
        }
    }

    public ICollection<TValue> Values
    {
        get
        {
            _readerWriterLock.EnterReadLock();
            try
            {
                return _dictionary.Values;
            }
            finally
            {
                _readerWriterLock.ExitReadLock();
            }
        }
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dictionary.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_dictionary).GetEnumerator();
    }
}

This class implements the full IDictionary<Tkey,TValue> interface. 该类实现完整的IDictionary<Tkey,TValue>接口。 The way this works is all insertions pass through TryAdd , if you are at or above the max size and try to insert a new member you get a false from TryAdd and a InvalidOperationException from methods that do not return bool . 这种方式的工作方式是所有插入都通过TryAdd ,如果你达到或超过最大大小并尝试插入一个新成员,你会从TryAdd获得一个false ,而来自不返回bool方法的InvalidOperationException

The reason I did not use a ConcurrentDictionary is there is no good way to try to check the count before adding a new member in an atomic way, so you would need to lock anyway. 我没有使用ConcurrentDictionary的原因是在以原子方式添加新成员之前尝试检查计数没有好办法,所以无论如何都需要锁定。 You could potentially use a concurrent dictionary and remove all of my EnterReadLock 's and replace the EnterWriteLock 's with normal lock calls, but you would need to do performance testing to see which would do better. 您可以使用并发字典并删除我的所有EnterReadLock ,并用正常的lock调用替换EnterWriteLock ,但是您需要进行性能测试以查看哪个会做得更好。

If you want methods like GetOrAdd it would not be hard to implement yourself. 如果你想要像GetOrAdd这样的方法,你自己就不会很难实现。

If you need to create something like a ConcurrentDictionary with some extra features(eg max elements) I'd go for an Adaptor that will hold a private ConcurrentDictionary and expand it where you need to expand it. 如果你需要使用一些额外的功能(例如max elements)创建类似ConcurrentDictionary东西,我会选择一个包含私有ConcurrentDictionaryAdaptor ,并在需要扩展它的地方展开它。

A lot of method calls will stay with no change(you will simple call your private ConcurrentDictionary and do nothing). 许多方法调用将保持不变(您将简单地调用您的私有ConcurrentDictionary并且什么都不做)。

You'll end up with custom implementation anyways, that said there's no built in type that behaves dictionary-like and has capacity limitations... 无论如何,你最终会得到自定义实现,它表示没有类似字典的内置类型,并且具有容量限制......

To make it completely custom, you may go for ConcurrentHashSet limiting amount of entries will work for you. 要使其完全自定义,您可以选择ConcurrentHashSet限制条目数量将适合您。

Concurrent HashSet<T> in .NET Framework? .NET Framework中的并发HashSet <T>?

If you have all these additional requirements isn't it better to create a class that composes a List rather than is one? 如果你有所有这些额外的要求是不是更好的创建一个组成一个List而不是一个? Put the list inside the class you're making. 将列表放在您正在制作的课程中。

For example, I would say a chat room contains a list rather than being a special type of list. 例如,我会说聊天室包含一个列表而不是一个特殊类型的列表。 I would have all the max number, get chatter by name etc logic separate from the actual list . 我将拥有所有最大数量,通过名称等逻辑与实际list分开。 Then I would use a lock around interactions with the list, or some threadsafe collection like ConcurrentBag . 然后我会使用lock与列表的交互,或一些线程安全的集合,ConcurrentBag As far a whether you want a dictionary, it really depends on teh detail of the data and how you're going to be accessing it. 至于你是否想要一本字典,它实际上取决于数据的详细信息以及你将如何访问它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM