Потокобезопасный сбор с верхней границей

Я после коллекции со следующими свойствами:

  • threadsafe: он будет использоваться в asp.net, и несколько клиентов могут пытаться добавлять, удалять и получать доступ к элементам одновременно
  • максимальные элементы: я хочу иметь возможность установить верхнюю границу, максимальное количество элементов, во время построения
  • TryAdd: метод, который работает так же, как BlockingCollection<T>.TryAdd(T), будет идеальным, то есть он вернет false, если достигнут максимальное количество элементов.
  • Словарь типа. В большинстве других случаев ConcurrentDictionary был бы идеальным, то есть способностью идентифицировать элементы ключом, удалить любой элемент (не только первый или последний, который, я думаю, это ограничение с BlockingCollection)

Прежде чем я попытаюсь опрокинуть свои собственные, мои вопросы:

  • Я пропустил встроенный тип, который поставил бы безопасный потолок на количество элементов в коллекции?
  • Есть ли способ достичь этой функциональности с BlockingCollection каким-то образом?

Наконец, если мне нужно попытаться сделать свой собственный, о каком подходе я должен думать? Это просто, как обернутый Dictionary с locks?

Пример использования: Чат-комната с определенным лимитом на количество участников может хранить информацию о соединении участников и отклонять новых участников, пока не будет места для ввода, когда будет полностью

Ответ 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<Tkey,TValue>. Как это работает, все вставки проходят через TryAdd, если вы находитесь на уровне или выше максимального размера и пытаетесь вставить новый член, вы получаете false из TryAdd и InvalidOperationException из методов, которые не возвращаются bool.

Причина, по которой я не использовал ConcurrentDictionary, - это не лучший способ проверить счетчик перед добавлением нового члена в atomic, поэтому вам все равно придется блокировать. Вы могли бы использовать параллельный словарь и удалить все мои EnterReadLock и заменить EnterWriteLock на обычные вызовы lock, но вам нужно будет выполнить тестирование производительности, чтобы увидеть, что будет лучше.

Если вам нужны такие методы, как GetOrAdd, реализовать его не будет.

Ответ 2

Если вам нужно создать что-то вроде ConcurrentDictionary с некоторыми дополнительными функциями (например, max-элементами), я бы пошел на Adaptor, который будет содержать закрытый ConcurrentDictionary и расширять его там, где вам нужно его развернуть.

Многие вызовы методов будут оставаться без изменений (вы просто позвоните своему частному ConcurrentDictionary и ничего не сделаете).

Ответ 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);
        }
    }
}

Ответ 4

В любом случае вы получите пользовательскую реализацию, которая говорит, что нет встроенного типа, который ведет себя как словарь и имеет ограничения емкости...

Чтобы сделать его полностью обычным, вы можете пойти на ConcurrentHashSet ограничение количества записей будет работать для вас.

Параллельный HashSet <T> в .NET Framework?

Ответ 5

Если у вас есть все эти дополнительные требования, не лучше создать класс, который составлен a List а не одно? Поместите список внутри класса, который вы делаете.

Например, я бы сказал, что чат-комната содержит список, а не специальный список. У меня было бы максимальное максимальное число, идите по логике и т.д. Отдельно от фактического List. Затем я использовал бы lock вокруг взаимодействия со списком или некоторую коллекцию потокобезопасности, например ConcurrentBag. Насколько вам нужен словарь, он действительно зависит от деталей данных и от того, как вы собираетесь обращаться к нему.