2014-12-10 12 views
10

Sono dopo una raccolta con le seguenti proprietà:filo Collection sicuro con limite superiore

  • threadsafe: sarà utilizzato in asp.net e più client potrebbe cercare di aggiungere, rimuovere e membri di accesso contemporaneamente
  • elementi max: Voglio essere in grado di impostare un limite superiore, un numero massimo di elementi, in tempi di costruzione
  • TryAdd: un metodo che funziona come BlockingCollection<T>.TryAdd(T) woul d essere perfetto, cioè restituirebbe falso se il numero massimo di elementi è stato raggiunto
  • Dizionario-like: In molti altri aspetti uno ConcurrentDictionary sarebbe perfetto, cioè la capacità di identificare gli elementi con una chiave, rimuovere qualsiasi elemento (non solo il primo o l'ultimo, che credo sarebbe la limitazione con BlockingCollection)

prima tento di rotolare il mio, le mie domande sono:

  1. ho perso una costruito nel tipo che avrebbe mettere un limite sicuro sul numero di elementi in un collec zione?
  2. C'è un modo per ottenere questa funzionalità con BlockingCollection in qualche modo?

Infine, se ho bisogno di provare a farmi da solo, quale approccio dovrei pensare? È semplice come un Dictionary con locks? uso

Esempio: una chat room con un limite definito sul numero di partecipanti potrebbe memorizzare le informazioni di connessione dei partecipanti e rifiutano i nuovi entranti fino a quando non v'è spazio per entrare quando in pieno

risposta

2

La soluzione più semplice è solo fare una classe wrapper che utilizza un dizionario normale e utilizza un ReaderWriterLockSlim per controllare filo accesso sicuro.

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

Questa classe implementa l'interfaccia completa IDictionary<Tkey,TValue>. Il modo in cui tutto questo funziona è che tutti gli inserimenti passano attraverso TryAdd, se sei uguale o superiore alla dimensione massima e prova a inserire un nuovo membro ottieni false da TryAdd e un InvalidOperationException da metodi che non restituiscono bool.

Il motivo per cui non ho utilizzato un ConcurrentDictionary non è un buon modo per provare a controllare il conteggio prima di aggiungere un nuovo membro in un modo atomic, quindi è necessario bloccarlo comunque. Potresti potenzialmente utilizzare un dizionario concorrente e rimuovere tutti i miei EnterReadLock e sostituire lo EnterWriteLock con le normali chiamate lock, ma dovresti eseguire test delle prestazioni per vedere quale farebbe meglio.

Se si desiderano metodi come GetOrAdd, non sarebbe difficile da implementare.

+1

'Contains' e' CopyTo' iniziano con 'ExitReadLock'. Immagino che dovrebbe essere "EnterReadLock"? Cool implementazione, però, +1 – Default

+0

Questa è sicuramente una versione molto migliore di quello che avrei inventato se avessi provato a farlo da solo. Ottima risposta – ChrisT

+0

C'è un vantaggio nell'usare 'ReaderWriterLockSlim' sul modo" normale "di dichiarare un' oggetto readonly privato myLock = new object(); 'e quindi circondare tutte le cose critiche con' lock (myLock) {... } '? I blocchi "prova ... finalmente" dappertutto danneggiano la leggibilità (imho). – Corak

1

Vi ritroverete con comunque l'implementazione personalizzata ha detto che non esiste un tipo built-in che si comporta come un dizionario e ha limitazioni di capacità ...

Per renderlo completamente personalizzato, è possibile utilizzare ConcurrentHashSet quantità limitate di voci che funzioneranno per voi.

Concurrent HashSet<T> in .NET Framework?

+1

Interessante, penso che alla fine avrò il percorso implicito nella risposta di Nathan, ma per la pura domanda (meno l'esempio) penso che questa sarebbe la strada da percorrere – ChrisT

+1

Sì, c'è sempre molto più dietro scene e certe soluzioni hanno benefici quando si ha un quadro completo della situazione. Dubito che avrai a che fare con milioni di utenti, quindi le prestazioni di HashSet su Elenco non sono il problema. – Andrew

1

Se si dispone di tutti questi requisiti aggiuntivi non è meglio per creare una classe che composes un List piuttosto che è uno? Metti la lista all'interno della lezione che stai facendo.

Ad esempio, direi che una chat contiene un elenco piuttosto che un tipo speciale di elenco. Avrei tutto il numero massimo, otterrò chatter per nome, ecc logico, separato dall'effettivo list. Quindi userei un lock intorno alle interazioni con l'elenco, o un po 'di threadsafe collection come ConcurrentBag. A prescindere che tu voglia un dizionario, dipende molto dal dettaglio dei dati e da come lo accederai.

+1

Penso che tu abbia ragione, forse ho avuto troppa speranza che ci fosse qualcosa di incorporato che mi avrebbe aiutato a proteggermi da me stesso. Come dici tu, probabilmente avresti lasciato la responsabilità nel posto sbagliato – ChrisT

2

Se avete bisogno di creare qualcosa di simile a un ConcurrentDictionary con alcune caratteristiche extra (es. Elementi max) Mi piacerebbe andare per un Adaptor che conterrà un privato ConcurrentDictionary ed espanderlo in cui è necessario espanderlo.

Un sacco di chiamate di metodo rimarranno senza modifiche (sarà semplice chiamare il tuo privato ConcurrentDictionary e non fare nulla).

2

Ecco una semplice implementazione per questo:

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, indicizzatore? – Andrew

Problemi correlati