线程安全收集与上限

时间:2014-12-10 14:29:40

标签: c# .net icollection

我在拥有以下属性的集合之后:

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

在我尝试推销自己之前,我的问题是:

  1. 我错过了一个内置类型,它会对集合中的元素数量设置安全上限吗?
  2. 有没有办法以BlockingCollection某种方式实现此功能?
  3. 最后,如果我确实需要尝试自己做,我应该考虑什么方法?它是否与包裹Dictionary的{​​{1}}一样简单?

    使用示例: 具有参与人数限定限制的聊天室可以存储参与者的连接信息并拒绝新参赛者,直到有足够的空间进入

5 个答案:

答案 0 :(得分:3)

这是一个简单的实现:

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

答案 1 :(得分:2)

如果您需要创建具有一些额外功能的ConcurrentDictionary之类的东西(例如最大元素),我会选择Adaptor来保存私有ConcurrentDictionary并将其扩展到你需要扩展它。

许多方法调用将保持不变(您只需调用私有ConcurrentDictionary并且不执行任何操作)。

答案 2 :(得分:2)

最简单的解决方案是创建一个使用普通字典的包装类,并使用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<Tkey,TValue>接口。这种方法的工作方式是所有插入都通过TryAdd,如果您达到或超过最大尺寸并尝试插入新成员,则会从false获得TryAdd并{{1来自不返回InvalidOperationException的方法。

我没有使用bool的原因是在以atomic方式添加新成员之前尝试检查计数没有好办法,所以无论如何都需要锁定。您可以使用并发字典并删除我的所有ConcurrentDictionary并将EnterReadLock替换为普通的EnterWriteLock调用,但您需要进行性能测试才能看到做得更好。

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

答案 3 :(得分:1)

无论如何,你最终会得到自定义实现,它表示没有类似字典的内置类型,并且有容量限制......

要使其完全自定义,您可以选择ConcurrentHashSet限制数量的条目对您有用。

Concurrent HashSet<T> in .NET Framework?

答案 4 :(得分:1)

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

例如,我会说聊天室包含一个列表而不是一个特殊类型的列表。我将获得所有最大数量,通过名称等逻辑与实际list分开。然后,我会使用lock与列表进行互动,或者使用threadsafe collection之类的ConcurrentBag。至于你是否想要一本字典,它实际上取决于数据的详细信息以及你将如何访问它。