2014-12-10 4 views
10

Я после сбора со следующими свойствами:поточно Коллекция с верхней границей

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

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

  1. я пропустил встроенный тип, который будет поставить безопасный потолок на количество элементов в коллекторе Тион?
  2. Есть ли способ достичь этой функциональности с BlockingCollection?

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

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

ответ

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 и InvalidOperationException от методов, которые не возвращаются bool.

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

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

+1

'Содержит' и' CopyTo' начинается с 'ExitReadLock'. Думаю, это должно быть 'EnterReadLock'? Прохладная реализация, хотя, +1 – Default

+0

Это определенно намного лучшая версия того, что я бы придумал, если бы я попытался сделать это сам. Отличный ответ – ChrisT

+0

Есть ли польза от использования 'ReaderWriterLockSlim' над« обычным »способом объявления некоторого« частного объекта readonly myLock = new object(); », а затем окружающего все критические вещи с помощью' lock (myLock) {... } '? Блоки «try ... finally» повсюду повредили читаемость (imho). – Corak

1

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

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

Concurrent HashSet<T> in .NET Framework?

+1

Интересно, я думаю, что в конечном счете я собираюсь идти по маршруту, на который отвечает ответ Натана, но по чистому вопросу (минус пример) я думаю, что это будет способ пойти – ChrisT

+1

Да, всегда есть намного больше сцены и определенные решения действительно имеют преимущества, когда у вас есть полная картина ситуации. Я сомневаюсь, что вы будете иметь дело с миллионами пользователей, поэтому производительность HashSet over List не проблема. – Andrew

1

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

Например, я бы сказал, что в чате есть список, а не специальный список. Я бы получил все максимальное количество, получив болтовню по имени и т. Д., Отдельно от фактического list. Тогда я бы использовал lock вокруг взаимодействия со списком, или threadsafe collection, как ConcurrentBag. Насколько вам нужен словарь, это действительно зависит от деталей данных и от того, как вы собираетесь обращаться к нему.

+1

Я думаю, что вы правы, возможно, я слишком надеюсь, что есть что-то встроенное, которое поможет мне защитить себя от себя. Как вы говорите, вероятно, оставила бы ответственность не в том месте – ChrisT

2

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

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

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

GetOrAdd, indexer? – Andrew