summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs
diff options
context:
space:
mode:
authorKoundinya Veluri <kouvel@users.noreply.github.com>2017-09-08 17:14:09 -0700
committerGitHub <noreply@github.com>2017-09-08 17:14:09 -0700
commit3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2 (patch)
treeb6bb9f02ef9754c8b56d24f3ed9c53b1ef89be8d /src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs
parent3505d67c279cdb1b8231b53350a340ad34151c78 (diff)
downloadcoreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.tar.gz
coreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.tar.bz2
coreclr-3fc4180f8e0bf71e09a54f0f95ec7ec6a4f492f2.zip
Port some concurrent collection implementations with latest fixes from CoreFX into CoreCLR copies (#12939)
Diffstat (limited to 'src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs')
-rw-r--r--src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs606
1 files changed, 300 insertions, 306 deletions
diff --git a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs
index e8e99885c8..4111c5964e 100644
--- a/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs
+++ b/src/mscorlib/src/System/Collections/Concurrent/ConcurrentDictionary.cs
@@ -2,9 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-//
/*============================================================
**
+** Class: ConcurrentDictionary
**
**
** Purpose: A scalable dictionary for concurrent access
@@ -12,18 +12,13 @@
**
===========================================================*/
-using System;
-using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Contracts;
-using System.Runtime.InteropServices;
-using System.Runtime.Serialization;
-using System.Text;
+using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Threading;
-using System.Security;
namespace System.Collections.Concurrent
{
@@ -46,91 +41,78 @@ namespace System.Collections.Concurrent
/// Wrapping the three tables in a single object allows us to atomically
/// replace all tables at once.
/// </summary>
- private class Tables
+ private sealed class Tables
{
- internal readonly Node[] m_buckets; // A singly-linked list for each bucket.
- internal readonly object[] m_locks; // A set of locks, each guarding a section of the table.
- internal volatile int[] m_countPerLock; // The number of elements guarded by each lock.
- internal readonly IEqualityComparer<TKey> m_comparer; // Key equality comparer
+ 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 Tables(Node[] buckets, object[] locks, int[] countPerLock, IEqualityComparer<TKey> comparer)
+ internal Tables(Node[] buckets, object[] locks, int[] countPerLock)
{
- m_buckets = buckets;
- m_locks = locks;
- m_countPerLock = countPerLock;
- m_comparer = comparer;
+ _buckets = buckets;
+ _locks = locks;
+ _countPerLock = countPerLock;
}
}
- private volatile Tables m_tables; // Internal tables of the dictionary
- // NOTE: this is only used for compat reasons to serialize the comparer.
- // This should not be accessed from anywhere else outside of the serialization methods.
- internal IEqualityComparer<TKey> m_comparer;
- private readonly bool m_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 m_keyRehashCount;
-
- private int m_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;
+ private volatile Tables _tables; // Internal tables of the dictionary
+ private IEqualityComparer<TKey> _comparer; // Key equality comparer
+ private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock
+ private int _budget; // The maximum number of elements per lock before a resize operation is triggered
// 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;
+ private const int DefaultCapacity = 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;
+ // a concurrency level larger than MaxLockNumber into the constructor.
+ private const int MaxLockNumber = 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();
-
/// <summary>
/// Determines whether type TValue can be written atomically
/// </summary>
private static bool IsValueWriteAtomic()
{
- Type valueType = typeof(TValue);
- if (valueType.IsEnum)
- {
- valueType = Enum.GetUnderlyingType(valueType);
- }
-
//
// 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.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)
+ Type valueType = typeof(TValue);
+ if (!valueType.IsValueType)
+ {
+ return true;
+ }
+ if (valueType.IsEnum)
{
- isAtomic |= valueType == typeof(Double) || valueType == typeof(Int64);
+ valueType = Enum.GetUnderlyingType(valueType);
}
- return isAtomic;
+ switch (Type.GetTypeCode(valueType))
+ {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.Char:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.SByte:
+ case TypeCode.Single:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ return true;
+ case TypeCode.Int64:
+ case TypeCode.Double:
+ case TypeCode.UInt64:
+ return IntPtr.Size == 8;
+ default:
+ return false;
+ }
}
/// <summary>
@@ -139,7 +121,7 @@ namespace System.Collections.Concurrent
/// class that is empty, has the default concurrency level, has the default initial capacity, and
/// uses the default comparer for the key type.
/// </summary>
- public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer<TKey>.Default) { }
+ public ConcurrentDictionary() : this(DefaultConcurrencyLevel, DefaultCapacity, true, null) { }
internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<TKey> comparer)
{
@@ -151,7 +133,6 @@ namespace System.Collections.Concurrent
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ConcurrentDictionary_CapacityMustNotBeNegative);
}
- if (comparer == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparer);
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
// any buckets.
@@ -168,13 +149,13 @@ namespace System.Collections.Concurrent
int[] countPerLock = new int[locks.Length];
Node[] buckets = new Node[capacity];
- m_tables = new Tables(buckets, locks, countPerLock, comparer);
+ _tables = new Tables(buckets, locks, countPerLock);
- m_growLockArray = growLockArray;
- m_budget = buckets.Length / locks.Length;
+ _comparer = comparer ?? EqualityComparer<TKey>.Default;
+ _growLockArray = growLockArray;
+ _budget = buckets.Length / locks.Length;
}
-
/// <summary>
/// Attempts to add the specified key and value to the <see cref="ConcurrentDictionary{TKey,
/// TValue}"/>.
@@ -191,9 +172,9 @@ namespace System.Collections.Concurrent
/// contains too many elements.</exception>
public bool TryAdd(TKey key, TValue value)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
TValue dummy;
- return TryAddInternal(key, value, false, true, out dummy);
+ return TryAddInternal(key, _comparer.GetHashCode(key), value, false, true, out dummy);
}
/// <summary>
@@ -208,7 +189,7 @@ namespace System.Collections.Concurrent
/// (Nothing in Visual Basic).</exception>
public bool ContainsKey(TKey key)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
TValue throwAwayValue;
return TryGetValue(key, out throwAwayValue);
@@ -228,7 +209,7 @@ namespace System.Collections.Concurrent
/// (Nothing in Visual Basic).</exception>
public bool TryRemove(TKey key, out TValue value)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
return TryRemoveInternal(key, out value, false, default(TValue));
}
@@ -246,34 +227,33 @@ namespace System.Collections.Concurrent
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue)
{
+ int hashcode = _comparer.GetHashCode(key);
while (true)
{
- Tables tables = m_tables;
-
- IEqualityComparer<TKey> comparer = tables.m_comparer;
+ Tables tables = _tables;
int bucketNo, lockNo;
- GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length);
+ GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length);
- lock (tables.m_locks[lockNo])
+ 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 != m_tables)
+ // This should be a rare occurrence.
+ if (tables != _tables)
{
continue;
}
Node prev = null;
- for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next)
+ for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr._next)
{
- Assert((prev == null && curr == tables.m_buckets[bucketNo]) || prev.m_next == curr);
+ Debug.Assert((prev == null && curr == tables._buckets[bucketNo]) || prev._next == curr);
- if (comparer.Equals(curr.m_key, key))
+ if (hashcode == curr._hashcode && _comparer.Equals(curr._key, key))
{
if (matchValue)
{
- bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr.m_value);
+ bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr._value);
if (!valuesMatch)
{
value = default(TValue);
@@ -283,15 +263,15 @@ namespace System.Collections.Concurrent
if (prev == null)
{
- Volatile.Write<Node>(ref tables.m_buckets[bucketNo], curr.m_next);
+ Volatile.Write<Node>(ref tables._buckets[bucketNo], curr._next);
}
else
{
- prev.m_next = curr.m_next;
+ prev._next = curr._next;
}
- value = curr.m_value;
- tables.m_countPerLock[lockNo]--;
+ value = curr._value;
+ tables._countPerLock[lockNo]--;
return true;
}
prev = curr;
@@ -319,27 +299,32 @@ namespace System.Collections.Concurrent
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
public bool TryGetValue(TKey key, out TValue value)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
+ return TryGetValueInternal(key, _comparer.GetHashCode(key), out value);
+ }
- int bucketNo, lockNoUnused;
+ private bool TryGetValueInternal(TKey key, int hashcode, out TValue value)
+ {
+ Debug.Assert(_comparer.GetHashCode(key) == hashcode);
+
+ // We must capture the _buckets field in a local variable. It is set to a new table on each table resize.
+ Tables tables = _tables;
- // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize.
- Tables tables = m_tables;
- IEqualityComparer<TKey> comparer = tables.m_comparer;
- GetBucketAndLockNo(comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables.m_buckets.Length, tables.m_locks.Length);
+ int bucketNo = GetBucket(hashcode, tables._buckets.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<Node>(ref tables.m_buckets[bucketNo]);
+ // The Volatile.Read ensures that we have a copy of the reference to tables._buckets[bucketNo].
+ // This protects us from reading fields ('_hashcode', '_key', '_value' and '_next') of different instances.
+ Node n = Volatile.Read<Node>(ref tables._buckets[bucketNo]);
while (n != null)
{
- if (comparer.Equals(n.m_key, key))
+ if (hashcode == n._hashcode && _comparer.Equals(n._key, key))
{
- value = n.m_value;
+ value = n._value;
return true;
}
- n = n.m_next;
+ n = n._next;
}
value = default(TValue);
@@ -356,9 +341,9 @@ namespace System.Collections.Concurrent
{
AcquireAllLocks(ref locksAcquired);
- Tables newTables = new Tables(new Node[DEFAULT_CAPACITY], m_tables.m_locks, new int[m_tables.m_countPerLock.Length], m_tables.m_comparer);
- m_tables = newTables;
- m_budget = Math.Max(1, newTables.m_buckets.Length / newTables.m_locks.Length);
+ Tables newTables = new Tables(new Node[DefaultCapacity], _tables._locks, new int[_tables._countPerLock.Length]);
+ _tables = newTables;
+ _budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length);
}
finally
{
@@ -400,9 +385,9 @@ namespace System.Collections.Concurrent
int count = 0;
- for (int i = 0; i < m_tables.m_locks.Length && count >= 0; i++)
+ for (int i = 0; i < _tables._locks.Length && count >= 0; i++)
{
- count += m_tables.m_countPerLock[i];
+ count += _tables._countPerLock[i];
}
if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow
@@ -434,14 +419,18 @@ namespace System.Collections.Concurrent
int count = 0;
checked
{
- for (int i = 0; i < m_tables.m_locks.Length; i++)
+ for (int i = 0; i < _tables._locks.Length; i++)
{
- count += m_tables.m_countPerLock[i];
+ count += _tables._countPerLock[i];
}
}
- KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count];
+ if (count == 0)
+ {
+ return Array.Empty<KeyValuePair<TKey, TValue>>();
+ }
+ KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count];
CopyToPairs(array, 0);
return array;
}
@@ -454,16 +443,16 @@ namespace System.Collections.Concurrent
/// <summary>
/// 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.
+ /// Important: the caller must hold all locks in _locks before calling CopyToPairs.
/// </summary>
private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index)
{
- Node[] buckets = m_tables.m_buckets;
+ Node[] buckets = _tables._buckets;
for (int i = 0; i < buckets.Length; i++)
{
- for (Node current = buckets[i]; current != null; current = current.m_next)
+ for (Node current = buckets[i]; current != null; current = current._next)
{
- array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
+ array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value);
index++; //this should never flow, CopyToPairs is only called when there's no overflow risk
}
}
@@ -472,16 +461,16 @@ namespace System.Collections.Concurrent
/// <summary>
/// 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.
+ /// Important: the caller must hold all locks in _locks before calling CopyToEntries.
/// </summary>
private void CopyToEntries(DictionaryEntry[] array, int index)
{
- Node[] buckets = m_tables.m_buckets;
+ Node[] buckets = _tables._buckets;
for (int i = 0; i < buckets.Length; i++)
{
- for (Node current = buckets[i]; current != null; current = current.m_next)
+ for (Node current = buckets[i]; current != null; current = current._next)
{
- array[index] = new DictionaryEntry(current.m_key, current.m_value);
+ array[index] = new DictionaryEntry(current._key, current._value);
index++; //this should never flow, CopyToEntries is only called when there's no overflow risk
}
}
@@ -490,16 +479,16 @@ namespace System.Collections.Concurrent
/// <summary>
/// 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.
+ /// Important: the caller must hold all locks in _locks before calling CopyToObjects.
/// </summary>
private void CopyToObjects(object[] array, int index)
{
- Node[] buckets = m_tables.m_buckets;
+ Node[] buckets = _tables._buckets;
for (int i = 0; i < buckets.Length; i++)
{
- for (Node current = buckets[i]; current != null; current = current.m_next)
+ for (Node current = buckets[i]; current != null; current = current._next)
{
- array[index] = new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
+ array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value);
index++; //this should never flow, CopyToObjects is only called when there's no overflow risk
}
}
@@ -516,17 +505,18 @@ namespace System.Collections.Concurrent
/// </remarks>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
- Node[] buckets = m_tables.m_buckets;
+ 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].
+ // The Volatile.Read ensures that we have a copy of the reference to buckets[i].
+ // This protects us from reading fields ('_key', '_value' and '_next') of different instances.
Node current = Volatile.Read<Node>(ref buckets[i]);
while (current != null)
{
- yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
- current = current.m_next;
+ yield return new KeyValuePair<TKey, TValue>(current._key, current._value);
+ current = current._next;
}
}
}
@@ -537,39 +527,37 @@ namespace System.Collections.Concurrent
/// If key doesn't exist, we always add value and return true;
/// </summary>
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
- private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue)
+ private bool TryAddInternal(TKey key, int hashcode, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue)
{
+ Debug.Assert(_comparer.GetHashCode(key) == hashcode);
+
while (true)
{
int bucketNo, lockNo;
- int hashcode;
- Tables tables = m_tables;
- IEqualityComparer<TKey> comparer = tables.m_comparer;
- hashcode = comparer.GetHashCode(key);
- GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length);
+ Tables tables = _tables;
+ GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length);
bool resizeDesired = false;
bool lockTaken = false;
-
try
{
if (acquireLock)
- Monitor.Enter(tables.m_locks[lockNo], ref lockTaken);
+ 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 != m_tables)
+ // This should be a rare occurrence.
+ if (tables != _tables)
{
continue;
}
// Try to find this key in the bucket
Node prev = null;
- for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next)
+ for (Node node = tables._buckets[bucketNo]; node != null; node = node._next)
{
- Assert((prev == null && node == tables.m_buckets[bucketNo]) || prev.m_next == node);
- if (comparer.Equals(node.m_key, key))
+ Debug.Assert((prev == null && node == tables._buckets[bucketNo]) || prev._next == node);
+ if (hashcode == node._hashcode && _comparer.Equals(node._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
@@ -578,25 +566,25 @@ namespace System.Collections.Concurrent
{
if (s_isValueWriteAtomic)
{
- node.m_value = value;
+ node._value = value;
}
else
{
- Node newNode = new Node(node.m_key, value, hashcode, node.m_next);
+ Node newNode = new Node(node._key, value, hashcode, node._next);
if (prev == null)
{
- tables.m_buckets[bucketNo] = newNode;
+ Volatile.Write(ref tables._buckets[bucketNo], newNode);
}
else
{
- prev.m_next = newNode;
+ prev._next = newNode;
}
}
resultingValue = value;
}
else
{
- resultingValue = node.m_value;
+ resultingValue = node._value;
}
return false;
}
@@ -604,10 +592,10 @@ namespace System.Collections.Concurrent
}
// The key was not found in the bucket. Insert the key-value pair.
- Volatile.Write<Node>(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo]));
+ Volatile.Write<Node>(ref tables._buckets[bucketNo], new Node(key, value, hashcode, tables._buckets[bucketNo]));
checked
{
- tables.m_countPerLock[lockNo]++;
+ tables._countPerLock[lockNo]++;
}
//
@@ -615,7 +603,7 @@ namespace System.Collections.Concurrent
// 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.m_countPerLock[lockNo] > m_budget)
+ if (tables._countPerLock[lockNo] > _budget)
{
resizeDesired = true;
}
@@ -623,7 +611,7 @@ namespace System.Collections.Concurrent
finally
{
if (lockTaken)
- Monitor.Exit(tables.m_locks[lockNo]);
+ Monitor.Exit(tables._locks[lockNo]);
}
//
@@ -636,7 +624,7 @@ namespace System.Collections.Concurrent
//
if (resizeDesired)
{
- GrowTable(tables, tables.m_comparer, false, m_keyRehashCount);
+ GrowTable(tables);
}
resultingValue = value;
@@ -650,7 +638,7 @@ namespace System.Collections.Concurrent
/// <param name="key">The key of the value to get or set.</param>
/// <value>The value associated with the specified key. If the specified key is not found, a get
/// operation throws a
- /// <see cref="T:Sytem.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new
+ /// <see cref="T:System.Collections.Generic.KeyNotFoundException"/>, and a set operation creates a new
/// element with the specified key.</value>
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is a null reference
/// (Nothing in Visual Basic).</exception>
@@ -664,18 +652,34 @@ namespace System.Collections.Concurrent
TValue value;
if (!TryGetValue(key, out value))
{
- ThrowHelper.ThrowKeyNotFoundException();
+ ThrowKeyNotFoundException();
}
return value;
}
set
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
TValue dummy;
- TryAddInternal(key, value, true, true, out dummy);
+ TryAddInternal(key, _comparer.GetHashCode(key), value, true, true, out dummy);
}
}
+ // These exception throwing sites have been extracted into their own NoInlining methods
+ // as these are uncommonly needed and when inlined are observed to prevent the inlining
+ // of important methods like TryGetValue and ContainsKey.
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowKeyNotFoundException()
+ {
+ throw new KeyNotFoundException();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowKeyNullException()
+ {
+ throw new ArgumentNullException("key");
+ }
+
/// <summary>
/// Gets the number of key/value pairs contained in the <see
/// cref="ConcurrentDictionary{TKey,TValue}"/>.
@@ -692,31 +696,46 @@ namespace System.Collections.Concurrent
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")]
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 < m_tables.m_countPerLock.Length; i++)
- {
- count += m_tables.m_countPerLock[i];
- }
+ return GetCountInternal();
}
finally
{
// Release locks that have been acquired earlier
ReleaseLocks(0, acquiredLocks);
}
-
- return count;
}
}
+ /// <summary>
+ /// Gets the number of key/value pairs contained in the <see
+ /// cref="ConcurrentDictionary{TKey,TValue}"/>. Should only be used after all locks
+ /// have been acquired.
+ /// </summary>
+ /// <exception cref="T:System.OverflowException">The dictionary contains too many
+ /// elements.</exception>
+ /// <value>The number of key/value pairs contained in the <see
+ /// cref="ConcurrentDictionary{TKey,TValue}"/>.</value>
+ /// <remarks>Count has snapshot semantics and represents the number of items in the <see
+ /// cref="ConcurrentDictionary{TKey,TValue}"/>
+ /// at the moment when Count was accessed.</remarks>
+ private int GetCountInternal()
+ {
+ int count = 0;
+
+ // Compute the count, we allow overflow
+ for (int i = 0; i < _tables._countPerLock.Length; i++)
+ {
+ count += _tables._countPerLock[i];
+ }
+ return count;
+ }
/// <summary>
/// Gets a value that indicates whether the <see cref="ConcurrentDictionary{TKey,TValue}"/> is empty.
@@ -734,9 +753,9 @@ namespace System.Collections.Concurrent
// Acquire all locks
AcquireAllLocks(ref acquiredLocks);
- for (int i = 0; i < m_tables.m_countPerLock.Length; i++)
+ for (int i = 0; i < _tables._countPerLock.Length; i++)
{
- if (m_tables.m_countPerLock[i] != 0)
+ if (_tables._countPerLock[i] != 0)
{
return false;
}
@@ -771,7 +790,7 @@ namespace System.Collections.Concurrent
{
if (!TryAdd(key, value))
{
- ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_KeyAlreadyExisted);
+ ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_KeyAlreadyExisted, ExceptionArgument.key);
}
}
@@ -904,7 +923,8 @@ namespace System.Collections.Concurrent
/// name="keyValuePair"/> is a null reference (Nothing in Visual Basic).</exception>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
{
- if (keyValuePair.Key == null) ThrowHelper.ThrowArgumentNullException(ExceptionResource.ConcurrentDictionary_ItemKeyIsNull);
+ if (keyValuePair.Key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keyValuePair, ExceptionResource.ConcurrentDictionary_ItemKeyIsNull);
+
TValue throwAwayValue;
return TryRemoveInternal(keyValuePair.Key, out throwAwayValue, true, keyValuePair.Value);
}
@@ -950,17 +970,18 @@ namespace System.Collections.Concurrent
/// </exception>
void IDictionary.Add(object key, object value)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
- if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect);
+ if (key == null) ThrowKeyNullException();
+ if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect, ExceptionArgument.key);
- TValue typedValue = default(TValue);
+ TValue typedValue;
try
{
typedValue = (TValue)value;
}
catch (InvalidCastException)
{
- ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect);
+ ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect, ExceptionArgument.value);
+ return;
}
((IDictionary<TKey, TValue>)this).Add((TKey)key, typedValue);
@@ -978,9 +999,9 @@ namespace System.Collections.Concurrent
/// (Nothing in Visual Basic).</exception>
bool IDictionary.Contains(object key)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
- return (key is TKey) && ((ConcurrentDictionary<TKey, TValue>)this).ContainsKey((TKey)key);
+ return (key is TKey) && this.ContainsKey((TKey)key);
}
/// <summary>Provides an <see cref="T:System.Collections.Generics.IDictionaryEnumerator"/> for the
@@ -1038,12 +1059,12 @@ namespace System.Collections.Concurrent
/// (Nothing in Visual Basic).</exception>
void IDictionary.Remove(object key)
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
TValue throwAwayValue;
if (key is TKey)
{
- this.TryRemove((TKey)key, out throwAwayValue);
+ TryRemove((TKey)key, out throwAwayValue);
}
}
@@ -1080,10 +1101,10 @@ namespace System.Collections.Concurrent
{
get
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
TValue value;
- if (key is TKey && this.TryGetValue((TKey)key, out value))
+ if (key is TKey && TryGetValue((TKey)key, out value))
{
return value;
}
@@ -1092,10 +1113,10 @@ namespace System.Collections.Concurrent
}
set
{
- if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ if (key == null) ThrowKeyNullException();
- if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect);
- if (!(value is TValue)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect);
+ if (!(key is TKey)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfKeyIncorrect, ExceptionArgument.key);
+ if (!(value is TValue)) ThrowHelper.ThrowArgumentException(ExceptionResource.ConcurrentDictionary_TypeOfValueIncorrect, ExceptionArgument.value);
((ConcurrentDictionary<TKey, TValue>)this)[(TKey)key] = (TValue)value;
}
@@ -1133,13 +1154,13 @@ namespace System.Collections.Concurrent
try
{
AcquireAllLocks(ref locksAcquired);
- Tables tables = m_tables;
+ Tables tables = _tables;
int count = 0;
- for (int i = 0; i < tables.m_locks.Length && count >= 0; i++)
+ for (int i = 0; i < tables._locks.Length && count >= 0; i++)
{
- count += tables.m_countPerLock[i];
+ count += tables._countPerLock[i];
}
if (array.Length - count < index || count < 0) //"count" itself or "count + index" can overflow
@@ -1213,62 +1234,49 @@ namespace System.Collections.Concurrent
/// <summary>
/// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the
- /// table as a result of a race condition, the Tables instance that holds the table of buckets deemed too
+ /// table as a result of races, 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 <paramref name="rehashCount"/> will be used to ensure that we don't do two subsequent resizes
- /// because of a collision
+ /// the Tables instance has been replaced in the meantime or not.
/// </summary>
- private void GrowTable(Tables tables, IEqualityComparer<TKey> newComparer, bool regenerateHashKeys, int rehashCount)
+ private void GrowTable(Tables tables)
{
+ const int MaxArrayLength = 0X7FEFFFFF;
int locksAcquired = 0;
try
{
- // The thread that first obtains m_locks[0] will be the one doing the resize operation
+ // The thread that first obtains _locks[0] will be the one doing the resize operation
AcquireLocks(0, 1, ref locksAcquired);
- if (regenerateHashKeys && rehashCount == m_keyRehashCount)
+ // Make sure nobody resized the table while we were waiting for lock 0:
+ if (tables != _tables)
{
- // 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 = m_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;
}
- else
+
+ // 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++)
{
- // 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 != m_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;
- }
+ approxCount += tables._countPerLock[i];
+ }
- // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
- long approxCount = 0;
- for (int i = 0; i < tables.m_countPerLock.Length; 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)
{
- approxCount += tables.m_countPerLock[i];
+ _budget = int.MaxValue;
}
+ return;
+ }
- //
- // If the bucket array is too empty, double the budget instead of resizing the table
- //
- if (approxCount < tables.m_buckets.Length / 4)
- {
- m_budget = 2 * m_budget;
- if (m_budget < 0)
- {
- m_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;
@@ -1278,7 +1286,7 @@ namespace System.Collections.Concurrent
checked
{
// Double the size of the buckets table and add one, so that we have an odd integer.
- newLength = tables.m_buckets.Length * 2 + 1;
+ 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.
@@ -1287,9 +1295,9 @@ namespace System.Collections.Concurrent
newLength += 2;
}
- Assert(newLength % 2 != 0);
+ Debug.Assert(newLength % 2 != 0);
- if (newLength > Array.MaxArrayLength)
+ if (newLength > MaxArrayLength)
{
maximizeTableSize = true;
}
@@ -1302,28 +1310,27 @@ namespace System.Collections.Concurrent
if (maximizeTableSize)
{
- newLength = Array.MaxArrayLength;
+ 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.)
- m_budget = int.MaxValue;
+ _budget = int.MaxValue;
}
// Now acquire all other locks for the table
- AcquireLocks(1, tables.m_locks.Length, ref locksAcquired);
+ AcquireLocks(1, tables._locks.Length, ref locksAcquired);
- object[] newLocks = tables.m_locks;
+ object[] newLocks = tables._locks;
// Add more locks
- if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER)
+ if (_growLockArray && tables._locks.Length < MaxLockNumber)
{
- newLocks = new object[tables.m_locks.Length * 2];
- Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length);
-
- for (int i = tables.m_locks.Length; i < newLocks.Length; i++)
+ newLocks = new object[tables._locks.Length * 2];
+ Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length);
+ for (int i = tables._locks.Length; i < newLocks.Length; i++)
{
newLocks[i] = new object();
}
@@ -1333,24 +1340,16 @@ namespace System.Collections.Concurrent
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.m_buckets.Length; i++)
+ for (int i = 0; i < tables._buckets.Length; i++)
{
- Node current = tables.m_buckets[i];
+ Node current = tables._buckets[i];
while (current != null)
{
- Node next = current.m_next;
+ Node next = current._next;
int newBucketNo, newLockNo;
- int nodeHashCode = current.m_hashcode;
-
- if (regenerateHashKeys)
- {
- // Recompute the hash from the key
- nodeHashCode = newComparer.GetHashCode(current.m_key);
- }
+ GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length);
- GetBucketAndLockNo(nodeHashCode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length);
-
- newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, nodeHashCode, newBuckets[newBucketNo]);
+ newBuckets[newBucketNo] = new Node(current._key, current._value, current._hashcode, newBuckets[newBucketNo]);
checked
{
@@ -1361,22 +1360,11 @@ namespace System.Collections.Concurrent
}
}
- // 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
- {
- m_keyRehashCount++;
- }
- }
-
// Adjust the budget
- m_budget = Math.Max(1, newBuckets.Length / newLocks.Length);
+ _budget = Math.Max(1, newBuckets.Length / newLocks.Length);
// Replace tables with the new versions
- m_tables = new Tables(newBuckets, newLocks, newCountPerLock, newComparer);
+ _tables = new Tables(newBuckets, newLocks, newCountPerLock);
}
finally
{
@@ -1386,16 +1374,25 @@ namespace System.Collections.Concurrent
}
/// <summary>
+ /// Computes the bucket for a particular key.
+ /// </summary>
+ private static int GetBucket(int hashcode, int bucketCount)
+ {
+ int bucketNo = (hashcode & 0x7fffffff) % bucketCount;
+ Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
+ return bucketNo;
+ }
+
+ /// <summary>
/// Computes the bucket and lock number for a particular key.
/// </summary>
- private void GetBucketAndLockNo(
- int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
+ private static 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);
+ Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
+ Debug.Assert(lockNo >= 0 && lockNo < lockCount);
}
/// <summary>
@@ -1403,7 +1400,7 @@ namespace System.Collections.Concurrent
/// </summary>
private static int DefaultConcurrencyLevel
{
- get { return DEFAULT_CONCURRENCY_MULTIPLIER * PlatformHelper.ProcessorCount; }
+ get { return PlatformHelper.ProcessorCount; }
}
/// <summary>
@@ -1416,10 +1413,10 @@ namespace System.Collections.Concurrent
// 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, m_tables.m_locks.Length, ref locksAcquired);
- Assert(locksAcquired == m_tables.m_locks.Length);
+ // Now that we have lock 0, the _locks array will not change (i.e., grow),
+ // and so we can safely read _locks.Length.
+ AcquireLocks(1, _tables._locks.Length, ref locksAcquired);
+ Debug.Assert(locksAcquired == _tables._locks.Length);
}
/// <summary>
@@ -1429,8 +1426,8 @@ namespace System.Collections.Concurrent
/// </summary>
private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
{
- Assert(fromInclusive <= toExclusive);
- object[] locks = m_tables.m_locks;
+ Debug.Assert(fromInclusive <= toExclusive);
+ object[] locks = _tables._locks;
for (int i = fromInclusive; i < toExclusive; i++)
{
@@ -1455,11 +1452,11 @@ namespace System.Collections.Concurrent
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
private void ReleaseLocks(int fromInclusive, int toExclusive)
{
- Assert(fromInclusive <= toExclusive);
+ Debug.Assert(fromInclusive <= toExclusive);
for (int i = fromInclusive; i < toExclusive; i++)
{
- Monitor.Exit(m_tables.m_locks[i]);
+ Monitor.Exit(_tables._locks[i]);
}
}
@@ -1473,15 +1470,18 @@ namespace System.Collections.Concurrent
try
{
AcquireAllLocks(ref locksAcquired);
- List<TKey> keys = new List<TKey>();
- for (int i = 0; i < m_tables.m_buckets.Length; i++)
+ int count = GetCountInternal();
+ if (count < 0) ThrowHelper.ThrowOutOfMemoryException();
+
+ List<TKey> keys = new List<TKey>(count);
+ for (int i = 0; i < _tables._buckets.Length; i++)
{
- Node current = m_tables.m_buckets[i];
+ Node current = _tables._buckets[i];
while (current != null)
{
- keys.Add(current.m_key);
- current = current.m_next;
+ keys.Add(current._key);
+ current = current._next;
}
}
@@ -1503,15 +1503,18 @@ namespace System.Collections.Concurrent
try
{
AcquireAllLocks(ref locksAcquired);
- List<TValue> values = new List<TValue>();
- for (int i = 0; i < m_tables.m_buckets.Length; i++)
+ int count = GetCountInternal();
+ if (count < 0) ThrowHelper.ThrowOutOfMemoryException();
+
+ List<TValue> values = new List<TValue>(count);
+ for (int i = 0; i < _tables._buckets.Length; i++)
{
- Node current = m_tables.m_buckets[i];
+ Node current = _tables._buckets[i];
while (current != null)
{
- values.Add(current.m_value);
- current = current.m_next;
+ values.Add(current._value);
+ current = current._next;
}
}
@@ -1524,30 +1527,21 @@ namespace System.Collections.Concurrent
}
/// <summary>
- /// A helper method for asserts.
- /// </summary>
- [Conditional("DEBUG")]
- private void Assert(bool condition)
- {
- Debug.Assert(condition);
- }
-
- /// <summary>
/// A node in a singly-linked list representing a particular hash table bucket.
/// </summary>
- private class Node
+ private sealed class Node
{
- internal TKey m_key;
- internal TValue m_value;
- internal volatile Node m_next;
- internal int m_hashcode;
+ internal readonly TKey _key;
+ internal TValue _value;
+ internal volatile Node _next;
+ internal readonly int _hashcode;
internal Node(TKey key, TValue value, int hashcode, Node next)
{
- m_key = key;
- m_value = value;
- m_next = next;
- m_hashcode = hashcode;
+ _key = key;
+ _value = value;
+ _next = next;
+ _hashcode = hashcode;
}
}
@@ -1555,43 +1549,43 @@ namespace System.Collections.Concurrent
/// A private class to represent enumeration over the dictionary that implements the
/// IDictionaryEnumerator interface.
/// </summary>
- private class DictionaryEnumerator : IDictionaryEnumerator
+ private sealed class DictionaryEnumerator : IDictionaryEnumerator
{
- private IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; // Enumerator over the dictionary.
+ IEnumerator<KeyValuePair<TKey, TValue>> _enumerator; // Enumerator over the dictionary.
internal DictionaryEnumerator(ConcurrentDictionary<TKey, TValue> dictionary)
{
- m_enumerator = dictionary.GetEnumerator();
+ _enumerator = dictionary.GetEnumerator();
}
public DictionaryEntry Entry
{
- get { return new DictionaryEntry(m_enumerator.Current.Key, m_enumerator.Current.Value); }
+ get { return new DictionaryEntry(_enumerator.Current.Key, _enumerator.Current.Value); }
}
public object Key
{
- get { return m_enumerator.Current.Key; }
+ get { return _enumerator.Current.Key; }
}
public object Value
{
- get { return m_enumerator.Current.Value; }
+ get { return _enumerator.Current.Value; }
}
public object Current
{
- get { return this.Entry; }
+ get { return Entry; }
}
public bool MoveNext()
{
- return m_enumerator.MoveNext();
+ return _enumerator.MoveNext();
}
public void Reset()
{
- m_enumerator.Reset();
+ _enumerator.Reset();
}
}
}