【问题标题】:thread safe Collection with upper bound具有上限的线程安全集合
【发布时间】:2015-02-08 19:06:51
【问题描述】:

我正在收集具有以下属性的集合:

  • 线程安全:将在asp.net中使用,多个客户端可以尝试同时添加、删除和访问成员
  • 最大元素数:我希望能够在构建时设置一个上限,即最大元素数
  • TryAdd:与BlockingCollection<T>.TryAdd(T) 工作方式相同的方法将是完美的,即如果已达到最大元素数,它将返回 false
  • 类似字典:在大多数其他方面ConcurrentDictionary 将是完美的,即能够通过键识别元素,删除任何项目(不仅仅是第一个或最后一个,我认为这将是限制BlockingCollection)

在我尝试自己动手之前,我的问题是:

  1. 我是否错过了一个内置类型,该类型会对集合中的元素数量设置安全上限?
  2. 有没有办法通过BlockingCollection 以某种方式实现此功能?

最后,如果我确实需要尝试自己制作,我应该考虑什么方法?是不是像 Dictionarylocks 一样简单?

使用示例: 有明确参与者数量限制的聊天室可以存储参与者的连接信息并拒绝新进入者,直到有空间进入时才可以进入

【问题讨论】:

    标签: c# .net icollection


    【解决方案1】:

    最简单的解决方案是制作一个使用普通字典并使用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();
        }
    }
    

    这个类实现了完整的IDictionary&lt;Tkey,TValue&gt; 接口。这个工作的方式是所有插入都通过TryAdd,如果你达到或超过最大大小并尝试插入一个新成员,你会从TryAdd得到一个false,从不返回的方法中得到一个InvalidOperationException bool

    我没有使用ConcurrentDictionary 的原因是在以atomic 方式添加新成员之前没有好的方法可以尝试检查计数,因此无论如何您都需要锁定。您可能会使用并发字典并删除我的所有 EnterReadLock 并将 EnterWriteLock 替换为正常的 lock 调用,但您需要进行性能测试以确定哪个会更好。

    如果你想要像GetOrAdd 这样的方法,你自己实现并不难。

    【讨论】:

    • ContainsCopyToExitReadLock 开头。我想那应该是EnterReadLock?不过很酷的实现,+1
    • 如果我自己尝试做的话,这绝对是一个更好的版本。很好的答案
    • 使用ReaderWriterLockSlim 是否比声明一些private readonly object myLock = new object(); 然后用lock (myLock) { ... } 包围所有关键内容的“通常”方式有好处?到处都是try ... finally 块会损害可读性(恕我直言)。
    • 没关系,发现这个:stackoverflow.com/questions/4217398/…
    【解决方案2】:

    这是一个简单的实现:

    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);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果您需要创建类似ConcurrentDictionary 的东西,并带有一些额外的功能(例如最大元素),我会选择一个Adaptor,它将拥有一个私有ConcurrentDictionary,并将其扩展至您需要扩展的地方。

      许多方法调用将保持不变(您只需调用您的私人ConcurrentDictionary 并且什么都不做)。

      【讨论】:

        【解决方案4】:

        无论如何,您最终都会得到自定义实现,也就是说没有内置类型可以表现得像字典并且有容量限制...

        要使其完全自定义,您可以选择ConcurrentHashSet 限制条目数量将适合您。

        Concurrent HashSet<T> in .NET Framework?

        【讨论】:

        • 有趣,我认为最终我会走内森的回答所暗示的路线,但对于提出的纯粹问题(减去示例),我认为这将是要走的路
        • 是的,幕后总是有很多事情发生,当您全面了解情况时,某些解决方案确实有好处。我怀疑你会处理数百万用户,所以 HashSet 优于 List 的性能不是问题。
        【解决方案5】:

        如果您有所有这些额外的要求,创建一个composesList 的类不是更好吗?将列表放入您正在制作的课程中。

        例如,我会说聊天室包含一个列表,而不是一种特殊类型的列表。我将拥有所有最大数量,通过名称等逻辑与实际list 分开。然后我会在与列表交互时使用lock,或者一些threadsafe collection,比如ConcurrentBag。至于您是否需要字典,这实际上取决于数据的详细信息以及您将如何访问它。

        【讨论】:

        • 我认为你是对的,也许我太希望有内置的东西可以帮助我保护自己免受自己的伤害。正如你所说,那可能会把责任放在错误的地方
        猜你喜欢
        • 2011-05-29
        • 2019-02-10
        • 1970-01-01
        • 2012-05-27
        • 2013-12-10
        • 2011-04-07
        • 1970-01-01
        • 2011-02-28
        • 2011-12-15
        相关资源
        最近更新 更多