Synchronized Dictionary

By Matthew

This is just a simple dictionary object, which derives from System.Collections.Generic.Dictionary<TKey, TValue>.  The idea is to simply provide an efficient and thread-safe dictionary, where 99% of the access methods are reads.  The real-world application of this object is in a web-app: The application maintains a collection of all the valid user objects, for all users that are logged in.

   1: using System;
   2: using System.Collections;
   3: using System.Collections.Generic;
   4: using System.Threading;
   5:  
   6: namespace Utilities
   7: {
   8:     /// <summary>
   9:     /// Provides a thread-safe and lock-efficient dictionary.  Key must
  10:     /// implement the generic IComparable interface, and the value must
  11:     /// be a reference type.
  12:     /// Enumerations are thread-safe, but will block changes to the
  13:     /// dictionary.
  14:     /// The Keys and Values properties are NOT thread-safe and will throw
  15:     /// exceptions if the dictionary is modified while enumerating those
  16:     /// objects.
  17:     /// 
  18:     /// Matthew Potter
  19:     /// 2007-08-31
  20:     /// matthew@synapseware.com
  21:     /// 
  22:     /// You are free to use this code, as long as you leave my author
  23:     /// information intact.
  24:     /// </summary>
  25:     public class SyncLockDictionary<TKey, UValue> : Dictionary<TKey, UValue>
  26:         where TKey: IComparable<TKey>
  27:         where UValue: class
  28:     {
  29:         /// <summary>
  30:         /// Defines the read-lock timeout, in milliseconds.
  31:         /// </summary>
  32:         protected const int READLOCK_TIMEOUT = 100;
  33:  
  34:         /// <summary>
  35:         /// Defines the write-lock timeout, in milliseconds.
  36:         /// </summary>
  37:         protected const int WRITELOCK_TIMEOUT = 250;
  38:  
  39:         /// <summary>
  40:         /// The locking mechanism that provides the thread-safety
  41:         /// </summary>
  42:         private ReaderWriterLock _lock = new ReaderWriterLock ();
  43:  
  44:         /// <summary>
  45:         /// Default Constructor
  46:         /// </summary>
  47:         public SyncLockDictionary ()
  48:             : base ()
  49:         {}
  50:  
  51:         /// <summary>
  52:         /// Copy Constructor
  53:         /// </summary>
  54:         /// <param name="copy"></param>
  55:         public SyncLockDictionary (Dictionary<TKey, UValue> copy)
  56:             : base (copy)
  57:         {}
  58:  
  59:         /// <summary>
  60:         /// Constructor
  61:         /// </summary>
  62:         /// <param name="comparer"></param>
  63:         public SyncLockDictionary (IEqualityComparer<TKey> comparer)
  64:             : base (comparer)
  65:         {}
  66:  
  67:         /// <summary>
  68:         /// Constructor
  69:         /// </summary>
  70:         /// <param name="capacity"></param>
  71:         public SyncLockDictionary (int capacity)
  72:             : base (capacity)
  73:         {}
  74:  
  75:         /// <summary>
  76:         /// Constructor
  77:         /// </summary>
  78:         /// <param name="capacity"></param>
  79:         /// <param name="comparer"></param>
  80:         public SyncLockDictionary (int capacity, IEqualityComparer<TKey> comparer)
  81:             : base (capacity, comparer)
  82:         {
  83:         }
  84:  
  85:         /// <summary>
  86:         /// Add a new value to the dictionary.
  87:         /// </summary>
  88:         /// <param name="key"></param>
  89:         /// <param name="value"></param>
  90:         public new void Add (TKey key, UValue value)
  91:         {
  92:             try
  93:             {
  94:                 _lock.AcquireWriterLock (READLOCK_TIMEOUT);
  95:                 if (base.ContainsKey (key))
  96:                     base [key] = value;
  97:                 else
  98:                     base.Add (key, value);
  99:             }
 100:             finally
 101:             {
 102:                 // release reader lock
 103:                 _lock.ReleaseWriterLock ();
 104:             }
 105:         }
 106:  
 107:         /// <summary>
 108:         /// Remove a value from the dictionary.
 109:         /// </summary>
 110:         /// <param name="key"></param>
 111:         public new void Remove (TKey key)
 112:         {
 113:             try
 114:             {
 115:                 // get read lock and return if nothing to remove
 116:                 _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 117:                 if (!base.ContainsKey (key))
 118:                     return;
 119:  
 120:                 // upgrade to writer lock so we can safely remove the item
 121:                 LockCookie lc = _lock.UpgradeToWriterLock (WRITELOCK_TIMEOUT);
 122:  
 123:                 // remove the item
 124:                 base.Remove (key);
 125:  
 126:                 // downgrade writer lock
 127:                 _lock.DowngradeFromWriterLock (ref lc);
 128:             }
 129:             finally
 130:             {
 131:                 // release reader lock
 132:                 _lock.ReleaseReaderLock ();
 133:             }
 134:         }
 135:  
 136:         /// <summary>
 137:         /// Returns true if the specified key exists in the collection.
 138:         /// </summary>
 139:         /// <param name="key"></param>
 140:         /// <returns></returns>
 141:         public new bool ContainsKey (TKey key)
 142:         {
 143:             try
 144:             {
 145:                 _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 146:  
 147:                 return base.ContainsKey (key);
 148:             }
 149:             finally
 150:             { _lock.ReleaseReaderLock (); }
 151:         }
 152:  
 153:         /// <summary>
 154:         /// Returns true if the specified value exists in the collection.
 155:         /// </summary>
 156:         /// <param name="value"></param>
 157:         /// <returns></returns>
 158:         public new bool ContainsValue (UValue value)
 159:         {
 160:             try
 161:             {
 162:                 _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 163:  
 164:                 return base.ContainsValue (value);
 165:             }
 166:             finally
 167:             { _lock.ReleaseReaderLock (); }
 168:         }
 169:  
 170:         /// <summary>
 171:         /// Gets the value associated with the specified key.  Returns
 172:         /// false if the specified key does not exist in the dictionary.
 173:         /// </summary>
 174:         /// <param name="key"></param>
 175:         /// <param name="value"></param>
 176:         /// <returns></returns>
 177:         public new bool TryGetValue (TKey key, out UValue value)
 178:         {
 179:             try
 180:             {
 181:                 _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 182:  
 183:                 return base.TryGetValue (key, out value);
 184:             }
 185:             finally
 186:             { _lock.ReleaseReaderLock (); }
 187:         }
 188:  
 189:         /// <summary>
 190:         /// Returns the number of items currently in the dictionary.
 191:         /// </summary>
 192:         public new int Count
 193:         {
 194:             get
 195:             {
 196:                 try
 197:                 {
 198:                     _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 199:  
 200:                     return base.Count;
 201:                 }
 202:                 finally
 203:                 { _lock.ReleaseReaderLock (); }
 204:             }
 205:         }
 206:  
 207:         /// <summary>
 208:         /// Returns a thread-safe enumerator for this dictionary (changes
 209:         /// to the dictionary will not be reflected during an enumeration).
 210:         /// </summary>
 211:         /// <returns></returns>
 212:         public new SafeEnumerator GetEnumerator ()
 213:         {
 214:             try
 215:             {
 216:                 _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 217:  
 218:                 ArrayList items = new ArrayList ();
 219:                 Enumerator en = base.GetEnumerator ();
 220:                 while (en.MoveNext ())
 221:                     items.Add (en.Current);
 222:  
 223:                 return new SafeEnumerator (items);
 224:             }
 225:             finally
 226:             { _lock.ReleaseReaderLock (); }
 227:         }
 228:  
 229:         /// <summary>
 230:         /// Returns a reference to the Keys collection.
 231:         /// </summary>
 232:         public new SafeEnumerator Keys
 233:         {
 234:             get
 235:             {
 236:                 try
 237:                 {
 238:                     _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 239:  
 240:                     ArrayList items = new ArrayList ();
 241:                     foreach (object key in base.Keys)
 242:                         items.Add (key.ToString ());
 243:  
 244:                     return new SafeEnumerator (items);
 245:                 }
 246:                 finally
 247:                 { _lock.ReleaseReaderLock (); }
 248:             }
 249:         }
 250:  
 251:         /// <summary>
 252:         /// Returns a reference to the Values collection.
 253:         /// </summary>
 254:         public new SafeEnumerator Values
 255:         {
 256:             get
 257:             {
 258:                 try
 259:                 {
 260:                     _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 261:  
 262:                     ArrayList items = new ArrayList ();
 263:                     foreach (object key in base.Values)
 264:                         items.Add (key);
 265:  
 266:                     return new SafeEnumerator (items);
 267:                 }
 268:                 finally
 269:                 { _lock.ReleaseReaderLock (); }
 270:             }
 271:         }
 272:  
 273:         /// <summary>
 274:         /// Thread-safe accessor property.
 275:         /// </summary>
 276:         /// <param name="key"></param>
 277:         /// <returns></returns>
 278:         public new UValue this [TKey key]
 279:         {
 280:             get
 281:             {
 282:                 try
 283:                 {
 284:                     _lock.AcquireReaderLock (READLOCK_TIMEOUT);
 285:  
 286:                     UValue value = null;
 287:                     if (base.TryGetValue (key, out value))
 288:                         return value;
 289:  
 290:                     return null;
 291:                 }
 292:                 finally
 293:                 { _lock.ReleaseReaderLock (); }
 294:             }
 295:             set
 296:             {
 297:                 try
 298:                 {
 299:                     _lock.AcquireWriterLock (WRITELOCK_TIMEOUT);
 300:                     try
 301:                     {
 302:                         if (base.ContainsKey (key))
 303:                             base [key] = value;
 304:                         else
 305:                             base.Add (key, value);
 306:                     }
 307:                     finally
 308:                     { _lock.ReleaseWriterLock (); }
 309:                 }
 310:                 finally
 311:                 {}
 312:             }
 313:         }
 314:  
 315:         /// <summary>
 316:         /// Gets an object that can be used to synchronize
 317:         /// access to the dictionary.
 318:         /// </summary>
 319:         public object SyncRoot
 320:         {
 321:             get
 322:             { return this; }
 323:         }
 324:     }
 325:  
 326:     /// <summary>
 327:     /// Provides a thread-safe enumerator for a SyncLockDictionary object.
 328:     /// </summary>
 329:     public class SafeEnumerator : System.Collections.IEnumerator, IEnumerable
 330:     {
 331:         private IList    _items;
 332:         private int        _current;
 333:  
 334:  
 335:         /// <summary>
 336:         /// 
 337:         /// </summary>
 338:         /// <param name="synclock"></param>
 339:         public SafeEnumerator (IList items)
 340:         {
 341:             _items        = items;
 342:             _current    = -1;
 343:         }
 344:  
 345:         /// <summary>
 346:         /// 
 347:         /// </summary>
 348:         public object Current
 349:         {
 350:             get
 351:             {
 352:                 if (_current < 0 || 0 == _items.Count)
 353:                     return null;
 354:  
 355:                 return _items [_current];
 356:             }
 357:         }
 358:  
 359:         /// <summary>
 360:         /// 
 361:         /// </summary>
 362:         public void Dispose ()
 363:         {}
 364:  
 365:         /// <summary>
 366:         /// 
 367:         /// </summary>
 368:         object IEnumerator.Current
 369:         {
 370:             get
 371:             {
 372:                 return this.Current;
 373:             }
 374:         }
 375:  
 376:         /// <summary>
 377:         /// 
 378:         /// </summary>
 379:         /// <returns></returns>
 380:         public bool MoveNext ()
 381:         {
 382:             if (_current >= _items.Count)
 383:                 return false;
 384:  
 385:             _current++;
 386:             if (_current < _items.Count)
 387:                 return true;
 388:  
 389:             return false;
 390:         }
 391:  
 392:         /// <summary>
 393:         /// 
 394:         /// </summary>
 395:         public void Reset ()
 396:         {
 397:             _current = -1;
 398:         }
 399:  
 400:         /// <summary>
 401:         /// 
 402:         /// </summary>
 403:         /// <returns></returns>
 404:         IEnumerator IEnumerable.GetEnumerator ()
 405:         {
 406:             return _items.GetEnumerator ();
 407:         }
 408:     }
 409: }

Leave a Reply