diff --git a/src/Lifetime/HierarchicalLifetimeManager.cs b/src/Lifetime/HierarchicalLifetimeManager.cs index 749431ff..d94c9fb5 100644 --- a/src/Lifetime/HierarchicalLifetimeManager.cs +++ b/src/Lifetime/HierarchicalLifetimeManager.cs @@ -1,6 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. - -using System; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -16,7 +15,8 @@ public class HierarchicalLifetimeManager : SynchronizedLifetimeManager, { #region Fields - private readonly IDictionary _values = new Dictionary(); + private readonly IDictionary _values = + new ConcurrentDictionary(); #endregion diff --git a/src/Unity.Abstractions.csproj b/src/Unity.Abstractions.csproj index b13b389f..126f0fdc 100644 --- a/src/Unity.Abstractions.csproj +++ b/src/Unity.Abstractions.csproj @@ -22,6 +22,10 @@ Unity + + + + diff --git a/src/Utility/ConcurrentDictionary.cs b/src/Utility/ConcurrentDictionary.cs new file mode 100644 index 00000000..f6e46cd9 --- /dev/null +++ b/src/Utility/ConcurrentDictionary.cs @@ -0,0 +1,1945 @@ +using System.Collections.Generic; +using System.Threading; +using System.Reflection; +using System.Diagnostics; +using System.Collections.ObjectModel; + +namespace System.Collections.Concurrent +{ + /// + /// Represents a thread-safe collection of keys and values. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// All public and protected members of are thread-safe and may be used + /// concurrently from multiple threads. + /// + internal class ConcurrentDictionary : IDictionary, IDictionary, IReadOnlyDictionary + { + protected const int MaxArrayLength = 0x7fefffff; + + /// + /// Tables that hold the internal state of the ConcurrentDictionary + /// + /// Wrapping the three tables in a single object allows us to atomically + /// replace all tables at once. + /// + private class Tables + { + internal readonly Node[] _buckets; // A singly-linked list for each bucket. + internal readonly object[] _locks; // A set of locks, each guarding a section of the table. + internal volatile int[] _countPerLock; // The number of elements guarded by each lock. + internal readonly IEqualityComparer _comparer; // Key equality comparer + + internal Tables(Node[] buckets, object[] locks, int[] countPerLock, IEqualityComparer comparer) + { + _buckets = buckets; + _locks = locks; + _countPerLock = countPerLock; + _comparer = comparer; + } + } + + private volatile Tables _tables; // Internal tables of the dictionary + + private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock + + // How many times we resized becaused of collisions. + // This is used to make sure we don't resize the dictionary because of multi-threaded Add() calls + // that generate collisions. Whenever a GrowTable() should be the only place that changes this + private int _keyRehashCount; + + private int _budget; // The maximum number of elements per lock before a resize operation is triggered + + // The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the + // DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference + // and blocking, but also the more expensive operations that require all locks become (e.g. table + // resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good + // compromise. + private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4; + + // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making + // a trade-off between the size of a very small dictionary, and the number of resizes when + // constructing a large dictionary. Also, the capacity should not be divisible by a small prime. + private const int DEFAULT_CAPACITY = 31; + + // The maximum size of the striped lock that will not be exceeded when locks are automatically + // added as the dictionary grows. However, the user is allowed to exceed this limit by passing + // a concurrency level larger than MAX_LOCK_NUMBER into the constructor. + private const int MAX_LOCK_NUMBER = 1024; + + // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads) + private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); + + + /// + /// Determines whether type TValue can be written atomically + /// + private static bool IsValueWriteAtomic() + { + var valueType = typeof(TValue); + + // + // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without + // the risk of tearing. + // + // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf + // + bool isAtomic = + (valueType.GetTypeInfo().IsClass) + || valueType == typeof(Boolean) + || valueType == typeof(Char) + || valueType == typeof(Byte) + || valueType == typeof(SByte) + || valueType == typeof(Int16) + || valueType == typeof(UInt16) + || valueType == typeof(Int32) + || valueType == typeof(UInt32) + || valueType == typeof(Single); + + if (!isAtomic && IntPtr.Size == 8) + { + isAtomic |= valueType == typeof(Double) || valueType == typeof(Int64); + } + + return isAtomic; + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the key type. + /// + public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer.Default) { } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the default + /// comparer for the key type. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The initial number of elements that the + /// can contain. + /// is + /// less than 1. + /// is less than + /// 0. + public ConcurrentDictionary(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, false, EqualityComparer.Default) { } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency + /// level, has the default initial capacity, and uses the default comparer for the key type. + /// + /// The whose elements are copied to + /// the new + /// . + /// is a null reference + /// (Nothing in Visual Basic). + /// contains one or more + /// duplicate keys. + public ConcurrentDictionary(IEnumerable> collection) : this(collection, EqualityComparer.Default) { } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the specified + /// . + /// + /// The + /// implementation to use when comparing keys. + /// is a null reference + /// (Nothing in Visual Basic). + public ConcurrentDictionary(IEqualityComparer comparer) : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, comparer) { } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency level, has the default + /// initial capacity, and uses the specified + /// . + /// + /// The whose elements are copied to + /// the new + /// . + /// The + /// implementation to use when comparing keys. + /// is a null reference + /// (Nothing in Visual Basic). -or- + /// is a null reference (Nothing in Visual Basic). + /// + public ConcurrentDictionary(IEnumerable> collection, IEqualityComparer comparer) + : this(comparer) + { + if (collection == null) throw new ArgumentNullException("collection"); + + InitializeFromCollection(collection); + } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , + /// has the specified concurrency level, has the specified initial capacity, and uses the specified + /// . + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The whose elements are copied to the new + /// . + /// The implementation to use + /// when comparing keys. + /// + /// is a null reference (Nothing in Visual Basic). + /// -or- + /// is a null reference (Nothing in Visual Basic). + /// + /// + /// is less than 1. + /// + /// contains one or more duplicate keys. + public ConcurrentDictionary( + int concurrencyLevel, IEnumerable> collection, IEqualityComparer comparer) + : this(concurrencyLevel, DEFAULT_CAPACITY, false, comparer) + { + if (collection == null) throw new ArgumentNullException("collection"); + if (comparer == null) throw new ArgumentNullException("comparer"); + + InitializeFromCollection(collection); + } + + private void InitializeFromCollection(IEnumerable> collection) + { + TValue dummy; + foreach (KeyValuePair pair in collection) + { + if (pair.Key == null) throw new ArgumentNullException("key"); + + if (!TryAddInternal(pair.Key, pair.Value, false, false, out dummy)) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_SourceContainsDuplicateKeys")); + } + } + + if (_budget == 0) + { + _budget = _tables._buckets.Length / _tables._locks.Length; + } + + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level, has the specified initial capacity, and + /// uses the specified . + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The initial number of elements that the + /// can contain. + /// The + /// implementation to use when comparing keys. + /// + /// is less than 1. -or- + /// is less than 0. + /// + /// is a null reference + /// (Nothing in Visual Basic). + public ConcurrentDictionary(int concurrencyLevel, int capacity, IEqualityComparer comparer) + : this(concurrencyLevel, capacity, false, comparer) + { + } + + internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) + { + if (concurrencyLevel < 1) + { + throw new ArgumentOutOfRangeException("concurrencyLevel", GetResource("ConcurrentDictionary_ConcurrencyLevelMustBePositive")); + } + if (capacity < 0) + { + throw new ArgumentOutOfRangeException("capacity", GetResource("ConcurrentDictionary_CapacityMustNotBeNegative")); + } + if (comparer == null) throw new ArgumentNullException("comparer"); + + // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard + // any buckets. + if (capacity < concurrencyLevel) + { + capacity = concurrencyLevel; + } + + object[] locks = new object[concurrencyLevel]; + for (int i = 0; i < locks.Length; i++) + { + locks[i] = new object(); + } + + int[] countPerLock = new int[locks.Length]; + Node[] buckets = new Node[capacity]; + _tables = new Tables(buckets, locks, countPerLock, comparer); + + _growLockArray = growLockArray; + _budget = buckets.Length / locks.Length; + } + + + /// + /// Attempts to add the specified key and value to the . + /// + /// The key of the element to add. + /// The value of the element to add. The value can be a null reference (Nothing + /// in Visual Basic) for reference types. + /// true if the key/value pair was added to the + /// successfully; otherwise, false. + /// is null reference + /// (Nothing in Visual Basic). + /// The + /// contains too many elements. + public bool TryAdd(TKey key, TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + TValue dummy; + return TryAddInternal(key, value, false, true, out dummy); + } + + /// + /// Determines whether the contains the specified + /// key. + /// + /// The key to locate in the . + /// true if the contains an element with + /// the specified key; otherwise, false. + /// is a null reference + /// (Nothing in Visual Basic). + public bool ContainsKey(TKey key) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue throwAwayValue; + return TryGetValue(key, out throwAwayValue); + } + + /// + /// Attempts to remove and return the the value with the specified key from the + /// . + /// + /// The key of the element to remove and return. + /// When this method returns, contains the object removed from the + /// or the default value of + /// if the operation failed. + /// true if an object was removed successfully; otherwise, false. + /// is a null reference + /// (Nothing in Visual Basic). + public bool TryRemove(TKey key, out TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + return TryRemoveInternal(key, out value, false, default(TValue)); + } + + /// + /// Removes the specified key from the dictionary if it exists and returns its associated value. + /// If matchValue flag is set, the key will be removed only if is associated with a particular + /// value. + /// + /// The key to search for and remove if it exists. + /// The variable into which the removed value, if found, is stored. + /// Whether removal of the key is conditional on its value. + /// The conditional value to compare against if is true + /// + private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue) + { + while (true) + { + Tables tables = _tables; + + IEqualityComparer comparer = tables._comparer; + + int bucketNo, lockNo; + GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + lock (tables._locks[lockNo]) + { + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != _tables) + { + continue; + } + + Node prev = null; + for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr.m_next) + { + Assert((prev == null && curr == tables._buckets[bucketNo]) || prev.m_next == curr); + + if (comparer.Equals(curr.m_key, key)) + { + if (matchValue) + { + bool valuesMatch = EqualityComparer.Default.Equals(oldValue, curr.m_value); + if (!valuesMatch) + { + value = default(TValue); + return false; + } + } + + if (prev == null) + { + Volatile.Write(ref tables._buckets[bucketNo], curr.m_next); + } + else + { + prev.m_next = curr.m_next; + } + + value = curr.m_value; + tables._countPerLock[lockNo]--; + return true; + } + prev = curr; + } + } + + value = default(TValue); + return false; + } + } + + /// + /// Attempts to get the value associated with the specified key from the . + /// + /// The key of the value to get. + /// When this method returns, contains the object from + /// the + /// with the specified key or the default value of + /// , if the operation failed. + /// true if the key was found in the ; + /// otherwise, false. + /// is a null reference + /// (Nothing in Visual Basic). + public bool TryGetValue(TKey key, out TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + int bucketNo, lockNoUnused; + + // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize. + Tables tables = _tables; + IEqualityComparer comparer = tables._comparer; + GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables._buckets.Length, tables._locks.Length); + + // We can get away w/out a lock here. + // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. + Node n = Volatile.Read(ref tables._buckets[bucketNo]); + + while (n != null) + { + if (comparer.Equals(n.m_key, key)) + { + value = n.m_value; + return true; + } + n = n.m_next; + } + + value = default(TValue); + return false; + } + + /// + /// Compares the existing value for the specified key with a specified value, and if they're equal, + /// updates the key with a third value. + /// + /// The key whose value is compared with and + /// possibly replaced. + /// The value that replaces the value of the element with if the comparison results in equality. + /// The value that is compared to the value of the element with + /// . + /// true if the value with was equal to and replaced with ; otherwise, + /// false. + /// is a null + /// reference. + public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) + { + if (key == null) throw new ArgumentNullException("key"); + + IEqualityComparer valueComparer = EqualityComparer.Default; + + while (true) + { + int bucketNo; + int lockNo; + int hashcode; + + Tables tables = _tables; + IEqualityComparer comparer = tables._comparer; + + hashcode = comparer.GetHashCode(key); + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + lock (tables._locks[lockNo]) + { + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != _tables) + { + continue; + } + + // Try to find this key in the bucket + Node prev = null; + for (Node node = tables._buckets[bucketNo]; node != null; node = node.m_next) + { + Assert((prev == null && node == tables._buckets[bucketNo]) || prev.m_next == node); + if (comparer.Equals(node.m_key, key)) + { + if (valueComparer.Equals(node.m_value, comparisonValue)) + { + if (s_isValueWriteAtomic) + { + node.m_value = newValue; + } + else + { + Node newNode = new Node(node.m_key, newValue, hashcode, node.m_next); + + if (prev == null) + { + tables._buckets[bucketNo] = newNode; + } + else + { + prev.m_next = newNode; + } + } + + return true; + } + + return false; + } + + prev = node; + } + + //didn't find the key + return false; + } + } + } + + /// + /// Removes all keys and values from the . + /// + public void Clear() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + Tables newTables = new Tables(new Node[DEFAULT_CAPACITY], _tables._locks, new int[_tables._countPerLock.Length], _tables._comparer); + _tables = newTables; + _budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Copies the elements of the to an array of + /// type , starting at the + /// specified array index. + /// + /// The one-dimensional array of type + /// that is the destination of the elements copied from the . The array must have zero-based indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference + /// (Nothing in Visual Basic). + /// is less than + /// 0. + /// is equal to or greater than + /// the length of the . -or- The number of elements in the source + /// is greater than the available space from to the end of the destination + /// . + void ICollection>.CopyTo(KeyValuePair[] array, int index) + { + if (array == null) throw new ArgumentNullException("array"); + if (index < 0) throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative")); + + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + int count = 0; + + for (int i = 0; i < _tables._locks.Length && count >= 0; i++) + { + count += _tables._countPerLock[i]; + } + + if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow + { + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough")); + } + + CopyToPairs(array, index); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Copies the key and value pairs stored in the to a + /// new array. + /// + /// A new array containing a snapshot of key and value pairs copied from the . + public KeyValuePair[] ToArray() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + int count = 0; + checked + { + for (int i = 0; i < _tables._locks.Length; i++) + { + count += _tables._countPerLock[i]; + } + } + + KeyValuePair[] array = new KeyValuePair[count]; + + CopyToPairs(array, 0); + return array; + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToPairs. + /// + private void CopyToPairs(KeyValuePair[] array, int index) + { + Node[] buckets = _tables._buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new KeyValuePair(current.m_key, current.m_value); + index++; //this should never flow, CopyToPairs is only called when there's no overflow risk + } + } + } + + /// + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToEntries. + /// + private void CopyToEntries(DictionaryEntry[] array, int index) + { + Node[] buckets = _tables._buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new DictionaryEntry(current.m_key, current.m_value); + index++; //this should never flow, CopyToEntries is only called when there's no overflow risk + } + } + } + + /// + /// Copy dictionary contents to an array - shared implementation between ToArray and CopyTo. + /// + /// Important: the caller must hold all locks in m_locks before calling CopyToObjects. + /// + private void CopyToObjects(object[] array, int index) + { + Node[] buckets = _tables._buckets; + for (int i = 0; i < buckets.Length; i++) + { + for (Node current = buckets[i]; current != null; current = current.m_next) + { + array[index] = new KeyValuePair(current.m_key, current.m_value); + index++; //this should never flow, CopyToObjects is only called when there's no overflow risk + } + } + } + + /// Returns an enumerator that iterates through the . + /// An enumerator for the . + /// + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after was called. + /// + public IEnumerator> GetEnumerator() + { + Node[] buckets = _tables._buckets; + + for (int i = 0; i < buckets.Length; i++) + { + // The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i]. + Node current = Volatile.Read(ref buckets[i]); + + while (current != null) + { + yield return new KeyValuePair(current.m_key, current.m_value); + current = current.m_next; + } + } + } + + /// + /// Shared internal implementation for inserts and updates. + /// If key exists, we always return false; and if updateIfExists == true we force update with value; + /// If key doesn't exist, we always add value and return true; + /// + private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) + { + while (true) + { + int bucketNo, lockNo; + int hashcode; + + Tables tables = _tables; + IEqualityComparer comparer = tables._comparer; + hashcode = comparer.GetHashCode(key); + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + bool resizeDesired = false; + bool lockTaken = false; + + try + { + if (acquireLock) + Monitor.Enter(tables._locks[lockNo], ref lockTaken); + + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurence. + if (tables != _tables) + { + continue; + } + + // Try to find this key in the bucket + Node prev = null; + for (Node node = tables._buckets[bucketNo]; node != null; node = node.m_next) + { + Assert((prev == null && node == tables._buckets[bucketNo]) || prev.m_next == node); + if (comparer.Equals(node.m_key, key)) + { + // The key was found in the dictionary. If updates are allowed, update the value for that key. + // We need to create a new node for the update, in order to support TValue types that cannot + // be written atomically, since lock-free reads may be happening concurrently. + if (updateIfExists) + { + if (s_isValueWriteAtomic) + { + node.m_value = value; + } + else + { + Node newNode = new Node(node.m_key, value, hashcode, node.m_next); + if (prev == null) + { + tables._buckets[bucketNo] = newNode; + } + else + { + prev.m_next = newNode; + } + } + resultingValue = value; + } + else + { + resultingValue = node.m_value; + } + return false; + } + prev = node; + + } + + // The key was not found in the bucket. Insert the key-value pair. + Volatile.Write(ref tables._buckets[bucketNo], new Node(key, value, hashcode, tables._buckets[bucketNo])); + checked + { + tables._countPerLock[lockNo]++; + } + + // + // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table. + // It is also possible that GrowTable will increase the budget but won't resize the bucket table. + // That happens if the bucket table is found to be poorly utilized due to a bad hash function. + // + if (tables._countPerLock[lockNo] > _budget) + { + resizeDesired = true; + } + } + finally + { + if (lockTaken) + Monitor.Exit(tables._locks[lockNo]); + } + + // + // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table. + // + // Concurrency notes: + // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks. + // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0 + // and then verify that the table we passed to it as the argument is still the current table. + // + if (resizeDesired) + { + GrowTable(tables, tables._comparer, false, _keyRehashCount); + } + + resultingValue = value; + return true; + } + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. If the specified key is not found, a get + /// operation throws a + /// , and a set operation creates a new + /// element with the specified key. + /// is a null reference + /// (Nothing in Visual Basic). + /// The property is retrieved and + /// + /// does not exist in the collection. + public TValue this[TKey key] + { + get + { + TValue value; + if (!TryGetValue(key, out value)) + { + throw new KeyNotFoundException(); + } + return value; + } + set + { + if (key == null) throw new ArgumentNullException("key"); + TValue dummy; + TryAddInternal(key, value, true, true, out dummy); + } + } + + /// + /// Gets the number of key/value pairs contained in the . + /// + /// The dictionary contains too many + /// elements. + /// The number of key/value paris contained in the . + /// Count has snapshot semantics and represents the number of items in the + /// at the moment when Count was accessed. + public int Count + { + get + { + int count = 0; + + int acquiredLocks = 0; + try + { + // Acquire all locks + AcquireAllLocks(ref acquiredLocks); + + // Compute the count, we allow overflow + for (int i = 0; i < _tables._countPerLock.Length; i++) + { + count += _tables._countPerLock[i]; + } + + } + finally + { + // Release locks that have been acquired earlier + ReleaseLocks(0, acquiredLocks); + } + + return count; + } + } + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// The function used to generate a value for the key + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value for the key as returned by valueFactory + /// if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, Func valueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (valueFactory == null) throw new ArgumentNullException("valueFactory"); + + TValue resultingValue; + if (TryGetValue(key, out resultingValue)) + { + return resultingValue; + } + TryAddInternal(key, valueFactory(key), false, true, out resultingValue); + return resultingValue; + } + + /// + /// Adds a key/value pair to the + /// if the key does not already exist. + /// + /// The key of the element to add. + /// the value to be added, if the key does not already exist + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The value for the key. This will be either the existing value for the key if the + /// key is already in the dictionary, or the new value if the key was not in the dictionary. + public TValue GetOrAdd(TKey key, TValue value) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue resultingValue; + TryAddInternal(key, value, false, true, out resultingValue); + return resultingValue; + } + + /// + /// Adds a key/value pair to the if the key does not already + /// exist, or updates a key/value pair in the if the key + /// already exists. + /// + /// The key to be added or whose value should be updated + /// The function used to generate a value for an absent key + /// The function used to generate a new value for an existing key + /// based on the key's existing value + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The new value for the key. This will be either be the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present). + public TValue AddOrUpdate(TKey key, Func addValueFactory, Func updateValueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (addValueFactory == null) throw new ArgumentNullException("addValueFactory"); + if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); + + TValue newValue, resultingValue; + while (true) + { + TValue oldValue; + if (TryGetValue(key, out oldValue)) + //key exists, try to update + { + newValue = updateValueFactory(key, oldValue); + if (TryUpdate(key, newValue, oldValue)) + { + return newValue; + } + } + else //try add + { + newValue = addValueFactory(key); + if (TryAddInternal(key, newValue, false, true, out resultingValue)) + { + return resultingValue; + } + } + } + } + + /// + /// Adds a key/value pair to the if the key does not already + /// exist, or updates a key/value pair in the if the key + /// already exists. + /// + /// The key to be added or whose value should be updated + /// The value to be added for an absent key + /// The function used to generate a new value for an existing key based on + /// the key's existing value + /// is a null reference + /// (Nothing in Visual Basic). + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// The new value for the key. This will be either be the result of addValueFactory (if the key was + /// absent) or the result of updateValueFactory (if the key was present). + public TValue AddOrUpdate(TKey key, TValue addValue, Func updateValueFactory) + { + if (key == null) throw new ArgumentNullException("key"); + if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); + TValue newValue, resultingValue; + while (true) + { + TValue oldValue; + if (TryGetValue(key, out oldValue)) + //key exists, try to update + { + newValue = updateValueFactory(key, oldValue); + if (TryUpdate(key, newValue, oldValue)) + { + return newValue; + } + } + else //try add + { + if (TryAddInternal(key, addValue, false, true, out resultingValue)) + { + return resultingValue; + } + } + } + } + + + + /// + /// Gets a value that indicates whether the is empty. + /// + /// true if the is empty; otherwise, + /// false. + public bool IsEmpty + { + get + { + int acquiredLocks = 0; + try + { + // Acquire all locks + AcquireAllLocks(ref acquiredLocks); + + for (int i = 0; i < _tables._countPerLock.Length; i++) + { + if (_tables._countPerLock[i] != 0) + { + return false; + } + } + } + finally + { + // Release locks that have been acquired earlier + ReleaseLocks(0, acquiredLocks); + } + + return true; + } + } + + #region IDictionary members + + /// + /// Adds the specified key and value to the . + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// + /// An element with the same key already exists in the . + void IDictionary.Add(TKey key, TValue value) + { + if (!TryAdd(key, value)) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_KeyAlreadyExisted")); + } + } + + /// + /// Removes the element with the specified key from the . + /// + /// The key of the element to remove. + /// true if the element is successfully remove; otherwise false. This method also returns + /// false if + /// was not found in the original . + /// + /// is a null reference + /// (Nothing in Visual Basic). + bool IDictionary.Remove(TKey key) + { + TValue throwAwayValue; + return TryRemove(key, out throwAwayValue); + } + + /// + /// Gets a collection containing the keys in the . + /// + /// An containing the keys in the + /// . + public ICollection Keys + { + get { return GetKeys(); } + } + + /// + /// Gets an containing the keys of + /// the . + /// + /// An containing the keys of + /// the . + IEnumerable IReadOnlyDictionary.Keys + { + get { return GetKeys(); } + } + + /// + /// Gets a collection containing the values in the . + /// + /// An containing the values in + /// the + /// . + public ICollection Values + { + get { return GetValues(); } + } + + /// + /// Gets an containing the values + /// in the . + /// + /// An containing the + /// values in the . + IEnumerable IReadOnlyDictionary.Values + { + get { return GetValues(); } + } + #endregion + + #region ICollection> Members + + /// + /// Adds the specified value to the + /// with the specified key. + /// + /// The + /// structure representing the key and value to add to the . + /// The of is null. + /// The + /// contains too many elements. + /// An element with the same key already exists in the + /// + void ICollection>.Add(KeyValuePair keyValuePair) + { + ((IDictionary)this).Add(keyValuePair.Key, keyValuePair.Value); + } + + /// + /// Determines whether the + /// contains a specific key and value. + /// + /// The + /// structure to locate in the . + /// true if the is found in the ; otherwise, false. + bool ICollection>.Contains(KeyValuePair keyValuePair) + { + TValue value; + if (!TryGetValue(keyValuePair.Key, out value)) + { + return false; + } + return EqualityComparer.Default.Equals(value, keyValuePair.Value); + } + + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + /// true if the is + /// read-only; otherwise, false. For , this property always returns + /// false. + bool ICollection>.IsReadOnly + { + get { return false; } + } + + /// + /// Removes a key and value from the dictionary. + /// + /// The + /// structure representing the key and value to remove from the . + /// true if the key and value represented by is successfully + /// found and removed; otherwise, false. + /// The Key property of is a null reference (Nothing in Visual Basic). + bool ICollection>.Remove(KeyValuePair keyValuePair) + { + if (keyValuePair.Key == null) throw new ArgumentNullException(GetResource("ConcurrentDictionary_ItemKeyIsNull")); + + TValue throwAwayValue; + return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value); + } + + #endregion + + #region IEnumerable Members + + /// Returns an enumerator that iterates through the . + /// An enumerator for the . + /// + /// The enumerator returned from the dictionary is safe to use concurrently with + /// reads and writes to the dictionary, however it does not represent a moment-in-time snapshot + /// of the dictionary. The contents exposed through the enumerator may contain modifications + /// made to the dictionary after was called. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((ConcurrentDictionary)this).GetEnumerator(); + } + + #endregion + + #region IDictionary Members + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The object to use as the key. + /// The object to use as the value. + /// is a null reference + /// (Nothing in Visual Basic). + /// The dictionary contains too many + /// elements. + /// + /// is of a type that is not assignable to the key type of the . -or- + /// is of a type that is not assignable to , + /// the type of values in the . + /// -or- A value with the same key already exists in the . + /// + void IDictionary.Add(object key, object value) + { + if (key == null) throw new ArgumentNullException("key"); + if (!(key is TKey)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect")); + + TValue typedValue; + try + { + typedValue = (TValue)value; + } + catch (InvalidCastException) + { + throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect")); + } + + ((IDictionary)this).Add((TKey)key, typedValue); + } + + /// + /// Gets whether the contains an + /// element with the specified key. + /// + /// The key to locate in the . + /// true if the contains + /// an element with the specified key; otherwise, false. + /// is a null reference + /// (Nothing in Visual Basic). + bool IDictionary.Contains(object key) + { + if (key == null) throw new ArgumentNullException("key"); + + return (key is TKey) && ((ConcurrentDictionary)this).ContainsKey((TKey)key); + } + + /// Provides an for the + /// . + /// An for the . + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return new DictionaryEnumerator(this); + } + + /// + /// Gets a value indicating whether the has a fixed size. + /// + /// true if the has a + /// fixed size; otherwise, false. For , this property always + /// returns false. + bool IDictionary.IsFixedSize + { + get { return false; } + } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// true if the is + /// read-only; otherwise, false. For , this property always + /// returns false. + bool IDictionary.IsReadOnly + { + get { return false; } + } + + /// + /// Gets an containing the keys of the . + /// + /// An containing the keys of the . + ICollection IDictionary.Keys + { + get { return GetKeys(); } + } + + /// + /// Removes the element with the specified key from the . + /// + /// The key of the element to remove. + /// is a null reference + /// (Nothing in Visual Basic). + void IDictionary.Remove(object key) + { + if (key == null) throw new ArgumentNullException("key"); + + TValue throwAwayValue; + if (key is TKey) + { + this.TryRemove((TKey)key, out throwAwayValue); + } + } + + /// + /// Gets an containing the values in the . + /// + /// An containing the values in the . + ICollection IDictionary.Values + { + get { return GetValues(); } + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key, or a null reference (Nothing in Visual Basic) + /// if is not in the dictionary or is of a type that is + /// not assignable to the key type of the . + /// is a null reference + /// (Nothing in Visual Basic). + /// + /// A value is being assigned, and is of a type that is not assignable to the + /// key type of the . -or- A value is being + /// assigned, and is of a type that is not assignable to the value type + /// of the + /// + object IDictionary.this[object key] + { + get + { + if (key == null) throw new ArgumentNullException("key"); + + TValue value; + if (key is TKey && this.TryGetValue((TKey)key, out value)) + { + return value; + } + + return null; + } + set + { + if (key == null) throw new ArgumentNullException("key"); + + if (!(key is TKey)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfKeyIncorrect")); + if (!(value is TValue)) throw new ArgumentException(GetResource("ConcurrentDictionary_TypeOfValueIncorrect")); + + ((ConcurrentDictionary)this)[(TKey)key] = (TValue)value; + } + } + + #endregion + + #region ICollection Members + + /// + /// Copies the elements of the to an array, starting + /// at the specified array index. + /// + /// The one-dimensional array that is the destination of the elements copied from + /// the . The array must have zero-based + /// indexing. + /// The zero-based index in at which copying + /// begins. + /// is a null reference + /// (Nothing in Visual Basic). + /// is less than + /// 0. + /// is equal to or greater than + /// the length of the . -or- The number of elements in the source + /// is greater than the available space from to the end of the destination + /// . + void ICollection.CopyTo(Array array, int index) + { + if (array == null) throw new ArgumentNullException("array"); + if (index < 0) throw new ArgumentOutOfRangeException("index", GetResource("ConcurrentDictionary_IndexIsNegative")); + + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + Tables tables = _tables; + + int count = 0; + + for (int i = 0; i < tables._locks.Length && count >= 0; i++) + { + count += tables._countPerLock[i]; + } + + if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow + { + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayNotLargeEnough")); + } + + // To be consistent with the behavior of ICollection.CopyTo() in Dictionary, + // we recognize three types of target arrays: + // - an array of KeyValuePair structs + // - an array of DictionaryEntry structs + // - an array of objects + + KeyValuePair[] pairs = array as KeyValuePair[]; + if (pairs != null) + { + CopyToPairs(pairs, index); + return; + } + + DictionaryEntry[] entries = array as DictionaryEntry[]; + if (entries != null) + { + CopyToEntries(entries, index); + return; + } + + object[] objects = array as object[]; + if (objects != null) + { + CopyToObjects(objects, index); + return; + } + + throw new ArgumentException(GetResource("ConcurrentDictionary_ArrayIncorrectType"), "array"); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Gets a value indicating whether access to the is + /// synchronized with the SyncRoot. + /// + /// true if access to the is synchronized + /// (thread safe); otherwise, false. For , this property always + /// returns false. + bool ICollection.IsSynchronized + { + get { return false; } + } + + /// + /// Gets an object that can be used to synchronize access to the . This property is not supported. + /// + /// The SyncRoot property is not supported. + object ICollection.SyncRoot + { + get + { + throw new NotSupportedException("ConcurrentCollection_SyncRoot_NotSupported"); + } + } + + #endregion + + /// + /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the + /// table as a result of ----s, the Tables instance that holds the table of buckets deemed too + /// small is passed in as an argument to GrowTable(). GrowTable() obtains a lock, and then checks + /// the Tables instance has been replaced in the meantime or not. + /// The will be used to ensure that we don't do two subsequent resizes + /// because of a collision + /// + private void GrowTable(Tables tables, IEqualityComparer newComparer, bool regenerateHashKeys, int rehashCount) + { + int locksAcquired = 0; + try + { + // The thread that first obtains m_locks[0] will be the one doing the resize operation + AcquireLocks(0, 1, ref locksAcquired); + + if (regenerateHashKeys && rehashCount == _keyRehashCount) + { + // This method is called with regenerateHashKeys==true when we detected + // more than HashHelpers.HashCollisionThreshold collisions when adding a new element. + // In that case we are in the process of switching to another (randomized) comparer + // and we have to re-hash all the keys in the table. + // We are only going to do this if we did not just rehash the entire table while waiting for the lock + tables = _tables; + } + else + { + // If we don't require a regeneration of hash keys we want to make sure we don't do work when + // we don't have to + if (tables != _tables) + { + // We assume that since the table reference is different, it was already resized (or the budget + // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, + // we will have to revisit this logic. + return; + } + + // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. + long approxCount = 0; + for (int i = 0; i < tables._countPerLock.Length; i++) + { + approxCount += tables._countPerLock[i]; + } + + // + // If the bucket array is too empty, double the budget instead of resizing the table + // + if (approxCount < tables._buckets.Length / 4) + { + _budget = 2 * _budget; + if (_budget < 0) + { + _budget = int.MaxValue; + } + + return; + } + } + // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by + // 2,3,5 or 7. We can consider a different table-sizing policy in the future. + int newLength = 0; + bool maximizeTableSize = false; + try + { + checked + { + // Double the size of the buckets table and add one, so that we have an odd integer. + newLength = tables._buckets.Length * 2 + 1; + + // Now, we only need to check odd integers, and find the first that is not divisible + // by 3, 5 or 7. + while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) + { + newLength += 2; + } + + Assert(newLength % 2 != 0); + + if (newLength > MaxArrayLength) + { + maximizeTableSize = true; + } + } + } + catch (OverflowException) + { + maximizeTableSize = true; + } + + if (maximizeTableSize) + { + newLength = MaxArrayLength; + + // We want to make sure that GrowTable will not be called again, since table is at the maximum size. + // To achieve that, we set the budget to int.MaxValue. + // + // (There is one special case that would allow GrowTable() to be called in the future: + // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.) + _budget = int.MaxValue; + } + + // Now acquire all other locks for the table + AcquireLocks(1, tables._locks.Length, ref locksAcquired); + + object[] newLocks = tables._locks; + + // Add more locks + if (_growLockArray && tables._locks.Length < MAX_LOCK_NUMBER) + { + newLocks = new object[tables._locks.Length * 2]; + Array.Copy(tables._locks, newLocks, tables._locks.Length); + + for (int i = tables._locks.Length; i < newLocks.Length; i++) + { + newLocks[i] = new object(); + } + } + + Node[] newBuckets = new Node[newLength]; + int[] newCountPerLock = new int[newLocks.Length]; + + // Copy all data into a new table, creating new nodes for all elements + for (int i = 0; i < tables._buckets.Length; i++) + { + Node current = tables._buckets[i]; + while (current != null) + { + Node next = current.m_next; + int newBucketNo, newLockNo; + int nodeHashCode = current.m_hashcode; + + if (regenerateHashKeys) + { + // Recompute the hash from the key + nodeHashCode = newComparer.GetHashCode(current.m_key); + } + + GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); + + newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, newBuckets[newBucketNo]); + + checked + { + newCountPerLock[newLockNo]++; + } + + current = next; + } + } + + // If this resize regenerated the hashkeys, increment the count + if (regenerateHashKeys) + { + // We use unchecked here because we don't want to throw an exception if + // an overflow happens + unchecked + { + _keyRehashCount++; + } + } + + // Adjust the budget + _budget = Math.Max(1, newBuckets.Length / newLocks.Length); + + // Replace tables with the new versions + _tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer); + } + finally + { + // Release all locks that we took earlier + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Computes the bucket and lock number for a particular key. + /// + private void GetBucketAndLockNo( + int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; + + Assert(bucketNo >= 0 && bucketNo < bucketCount); + Assert(lockNo >= 0 && lockNo < lockCount); + } + + /// + /// The number of concurrent writes for which to optimize by default. + /// + private static int DefaultConcurrencyLevel + { + + get { return DEFAULT_CONCURRENCY_MULTIPLIER * Environment.ProcessorCount; } + } + + /// + /// Acquires all locks for this hash table, and increments locksAcquired by the number + /// of locks that were successfully acquired. The locks are acquired in an increasing + /// order. + /// + private void AcquireAllLocks(ref int locksAcquired) + { + // First, acquire lock 0 + AcquireLocks(0, 1, ref locksAcquired); + + // Now that we have lock 0, the m_locks array will not change (i.e., grow), + // and so we can safely read m_locks.Length. + AcquireLocks(1, _tables._locks.Length, ref locksAcquired); + Assert(locksAcquired == _tables._locks.Length); + } + + /// + /// Acquires a contiguous range of locks for this hash table, and increments locksAcquired + /// by the number of locks that were successfully acquired. The locks are acquired in an + /// increasing order. + /// + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) + { + Assert(fromInclusive <= toExclusive); + object[] locks = _tables._locks; + + for (int i = fromInclusive; i < toExclusive; i++) + { + bool lockTaken = false; + try + { + Monitor.Enter(locks[i], ref lockTaken); + } + finally + { + if (lockTaken) + { + locksAcquired++; + } + } + } + } + + /// + /// Releases a contiguous range of locks. + /// + private void ReleaseLocks(int fromInclusive, int toExclusive) + { + Assert(fromInclusive <= toExclusive); + + for (int i = fromInclusive; i < toExclusive; i++) + { + Monitor.Exit(_tables._locks[i]); + } + } + + /// + /// Gets a collection containing the keys in the dictionary. + /// + private ReadOnlyCollection GetKeys() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + List keys = new List(); + + for (int i = 0; i < _tables._buckets.Length; i++) + { + Node current = _tables._buckets[i]; + while (current != null) + { + keys.Add(current.m_key); + current = current.m_next; + } + } + + return new ReadOnlyCollection(keys); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Gets a collection containing the values in the dictionary. + /// + private ReadOnlyCollection GetValues() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + List values = new List(); + + for (int i = 0; i < _tables._buckets.Length; i++) + { + Node current = _tables._buckets[i]; + while (current != null) + { + values.Add(current.m_value); + current = current.m_next; + } + } + + return new ReadOnlyCollection(values); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// A helper method for asserts. + /// + [Conditional("DEBUG")] + private void Assert(bool condition) + { + Debug.Assert(condition); + } + + /// + /// A helper function to obtain the string for a particular resource key. + /// + /// + /// + private string GetResource(string key) + { + Assert(key != null); + + return key;// TODO: Environment.GetResourceString(key); + } + + /// + /// A node in a singly-linked list representing a particular hash table bucket. + /// + private class Node + { + internal TKey m_key; + internal TValue m_value; + internal volatile Node m_next; + internal int m_hashcode; + + internal Node(TKey key, TValue value, int hashcode, Node next) + { + m_key = key; + m_value = value; + m_next = next; + m_hashcode = hashcode; + } + } + + /// + /// A private class to represent enumeration over the dictionary that implements the + /// IDictionaryEnumerator interface. + /// + private class DictionaryEnumerator : IDictionaryEnumerator + { + IEnumerator> m_enumerator; // Enumerator over the dictionary. + + internal DictionaryEnumerator(ConcurrentDictionary dictionary) + { + m_enumerator = dictionary.GetEnumerator(); + } + + public DictionaryEntry Entry + { + get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); } + } + + public object Key + { + get { return m_enumerator.Current.Key; } + } + + public object Value + { + get { return m_enumerator.Current.Value; } + } + + public object Current + { + get { return this.Entry; } + } + + public bool MoveNext() + { + return m_enumerator.MoveNext(); + } + + public void Reset() + { + m_enumerator.Reset(); + } + } + } +}