Сообщение Interlocked.CompareExchange против Volatile.Read от 27.01.2021 19:21
Изменено 04.02.2021 23:22 VladD2
Interlocked.CompareExchange против Volatile.Read
Почему код работает корректно,
а код, который даже более строгий, не выполняется как надо
Пример кода: https://dotnetfiddle.net/WPb7K3
public static T Read<T>(ref T location) where T : class?
{
// return Volatile.Read(ref location);
// Volatile.Read implementation
T obj = location;
Thread.MemoryBarrier();
return obj;
}
а код, который даже более строгий, не выполняется как надо
public static T Read<T>(ref T location) where T : class?
{
return Interlocked.CompareExchange(ref location, location, location);
}
Пример кода: https://dotnetfiddle.net/WPb7K3
Скрытый текст | |
//#define USE_INTERLOCKED // Uncomment to use Interlocked.CompareExchange version #nullable disable using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; public static class Volatile { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T ReadInterlocked<T>(ref T location) where T : class? => Interlocked.CompareExchange(ref location, location, location); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T ReadMemoryBarrier<T>(ref T location) where T : class? { // return Volatile.Read(ref location); // Volatile.Read implementation T obj = location; Thread.MemoryBarrier(); return obj; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Read<T>(ref T location) where T : class? => #if USE_INTERLOCKED ReadInterlocked(ref location); // Fails if called #else ReadMemoryBarrier(ref location); #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write<T>(ref T location, T value) where T : class? { Interlocked.Exchange(ref location, value); } } class Program { public static void Main() { for (int i = 0; i < 5; i++) { TestGetOrAddOrUpdate(5, 5, 5, 25000, true); } Console.WriteLine("Works"); } private static void TestGetOrAddOrUpdate(int cLevel, int initSize, int threads, int addsPerThread, bool isAdd) { var dict = new ConcurrentDictionary2<int, int>(cLevel, 1); var count = threads; using (var mre = new ManualResetEvent(false)) { for (var i = 0; i < threads; i++) { var ii = i; new Thread( () => { for (var j = 0; j < addsPerThread; j++) { if (isAdd) { dict.GetOrAdd(j, -j); } else { dict.AddOrUpdate(j, -j, (k, v) => -j); } } if (Interlocked.Decrement(ref count) == 0) { mre.Set(); } }).Start(); } mre.WaitOne(); } foreach (var pair in dict) { if (pair.Key != -pair.Value) throw new Exception("Fail"); } var gotKeys = new List<int>(); foreach (var pair in dict) { gotKeys.Add(pair.Key); } gotKeys.Sort(); var expectKeys = new List<int>(); for (var i = 0; i < addsPerThread; i++) { expectKeys.Add(i); } if (expectKeys.Count != gotKeys.Count) throw new Exception("Fail"); // Finally, let's verify that the count is reported correctly. if (addsPerThread != dict.Count) throw new Exception("Fail"); if (addsPerThread != dict.ToArray().Length) throw new Exception("Fail"); } } internal static partial class HashHelpers { /// <summary>Returns approximate reciprocal of the divisor: ceil(2**64 / divisor).</summary> /// <remarks>This should only be used on 64-bit.</remarks> public static ulong GetFastModMultiplier(uint divisor) => ulong.MaxValue / divisor + 1; /// <summary>Performs a mod operation using the multiplier pre-computed with <see cref="GetFastModMultiplier"/>.</summary> /// <remarks>This should only be used on 64-bit.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint FastMod(uint value, uint divisor, ulong multiplier) { // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), // which allows to avoid the long multiplication if the divisor is less than 2**31. Debug.Assert(divisor <= int.MaxValue); // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version // is faster than BigMul currently because we only need the high bits. uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); Debug.Assert(highbits == value % divisor); return highbits; } } /// <summary>Represents a thread-safe collection of keys and values.</summary> /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam> /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam> /// <remarks> /// All public and protected members of <see cref="ConcurrentDictionary2{TKey,TValue}"/> are thread-safe and may be used /// concurrently from multiple threads. /// </remarks> [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public class ConcurrentDictionary2<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary where TKey : notnull { /// <summary>Internal tables of the dictionary.</summary> private volatile Tables _tables; /// <summary>Key equality comparer.</summary> private readonly IEqualityComparer<TKey>? _comparer; /// <summary>Default comparer for TKey.</summary> /// <remarks> /// Used to avoid repeatedly accessing the shared default generic static, in particular for reference types where it's /// currently not devirtualized: https://github.com/dotnet/runtime/issues/10050. /// </remarks> private readonly EqualityComparer<TKey> _defaultComparer; /// <summary>Whether to dynamically increase the size of the striped lock.</summary> private readonly bool _growLockArray; /// <summary>The maximum number of elements per lock before a resize operation is triggered.</summary> private int _budget; /// <summary>The default capacity, i.e. the initial # of buckets.</summary> /// <remarks> /// 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. /// </remarks> private const int DefaultCapacity = 31; /// <summary> /// The maximum size of the striped lock that will not be exceeded when locks are automatically /// added as the dictionary grows. /// </summary> /// <remarks> /// The user is allowed to exceed this limit by passing /// a concurrency level larger than MaxLockNumber into the constructor. /// </remarks> private const int MaxLockNumber = 1024; /// <summary>Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads).</summary> private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic(); /// <summary>Determines whether type TValue can be written atomically.</summary> private static bool IsValueWriteAtomic() { // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without // the risk of tearing. See https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf if (!typeof(TValue).IsValueType || typeof(TValue) == typeof(IntPtr) || typeof(TValue) == typeof(UIntPtr)) { return true; } switch (Type.GetTypeCode(typeof(TValue))) { 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.Double: case TypeCode.Int64: case TypeCode.UInt64: return IntPtr.Size == 8; default: return false; } } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// 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 ConcurrentDictionary2() : this(DefaultConcurrencyLevel, DefaultCapacity, growLockArray: true, null) { } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that is empty, has the specified concurrency level and capacity, and uses the default /// comparer for the key type. /// </summary> /// <param name="concurrencyLevel">The estimated number of threads that will update the /// <see cref="ConcurrentDictionary2{TKey,TValue}"/> concurrently.</param> /// <param name="capacity">The initial number of elements that the <see cref="ConcurrentDictionary2{TKey,TValue}"/> can contain.</param> /// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception> /// <exception cref="ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than 0.</exception> public ConcurrentDictionary2(int concurrencyLevel, int capacity) : this(concurrencyLevel, capacity, growLockArray: false, null) { } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that contains elements copied from the specified <see cref="IEnumerable{T}"/>, has the default concurrency /// level, has the default initial capacity, and uses the default comparer for the key type. /// </summary> /// <param name="collection">The <see /// cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ConcurrentDictionary2{TKey,TValue}"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception> public ConcurrentDictionary2(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null) { } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that is empty, has the specified concurrency level and capacity, and uses the specified /// <see cref="IEqualityComparer{TKey}"/>. /// </summary> /// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param> public ConcurrentDictionary2(IEqualityComparer<TKey>? comparer) : this(DefaultConcurrencyLevel, DefaultCapacity, growLockArray: true, comparer) { } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that contains elements copied from the specified <see cref="IEnumerable"/>, has the default concurrency /// level, has the default initial capacity, and uses the specified <see cref="IEqualityComparer{TKey}"/>. /// </summary> /// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied to the new <see cref="ConcurrentDictionary2{TKey,TValue}"/>.</param> /// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param> /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception> public ConcurrentDictionary2(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer) : this(comparer) { if (collection is null) { throw new ArgumentNullException(nameof(collection)); } InitializeFromCollection(collection); } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that contains elements copied from the specified <see cref="IEnumerable"/>, /// has the specified concurrency level, has the specified initial capacity, and uses the specified /// <see cref="IEqualityComparer{TKey}"/>. /// </summary> /// <param name="concurrencyLevel"> /// The estimated number of threads that will update the <see cref="ConcurrentDictionary2{TKey,TValue}"/> concurrently. /// </param> /// <param name="collection">The <see cref="IEnumerable{T}"/> whose elements are copied to the new /// <see cref="ConcurrentDictionary2{TKey,TValue}"/>.</param> /// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param> /// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception> /// <exception cref="ArgumentException"><paramref name="collection"/> contains one or more duplicate keys.</exception> public ConcurrentDictionary2(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer) : this(concurrencyLevel, DefaultCapacity, growLockArray: false, comparer) { if (collection is null) { throw new ArgumentNullException(nameof(collection)); } InitializeFromCollection(collection); } private void InitializeFromCollection(IEnumerable<KeyValuePair<TKey, TValue>> collection) { foreach (KeyValuePair<TKey, TValue> pair in collection) { if (pair.Key is null) { throw new ArgumentNullException(nameof(pair.Key)); } if (!TryAddInternal(pair.Key, null, pair.Value, updateIfExists: false, acquireLock: false, out _)) { throw new ArgumentException("SourceContainsDuplicateKeys"); } } if (_budget == 0) { Tables tables = _tables; _budget = tables._buckets.Length / tables._locks.Length; } } /// <summary> /// Initializes a new instance of the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// class that is empty, has the specified concurrency level, has the specified initial capacity, and /// uses the specified <see cref="IEqualityComparer{TKey}"/>. /// </summary> /// <param name="concurrencyLevel">The estimated number of threads that will update the <see cref="ConcurrentDictionary2{TKey,TValue}"/> concurrently.</param> /// <param name="capacity">The initial number of elements that the <see cref="ConcurrentDictionary2{TKey,TValue}"/> can contain.</param> /// <param name="comparer">The <see cref="IEqualityComparer{TKey}"/> implementation to use when comparing keys.</param> /// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1. -or- <paramref name="capacity"/> is less than 0.</exception> public ConcurrentDictionary2(int concurrencyLevel, int capacity, IEqualityComparer<TKey>? comparer) : this(concurrencyLevel, capacity, growLockArray: false, comparer) { } internal ConcurrentDictionary2(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<TKey>? comparer) { if (concurrencyLevel < 1) { throw new ArgumentOutOfRangeException(nameof(concurrencyLevel), "ConcurrencyLevelMustBePositive"); } if (capacity < 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "CapacityMustNotBeNegative"); } // 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; } var locks = new object[concurrencyLevel]; locks[0] = locks; // reuse array as the first lock object just to avoid an additional allocation for (int i = 1; i < locks.Length; i++) { locks[i] = new object(); } var countPerLock = new int[locks.Length]; var buckets = new Node[capacity]; _tables = new Tables(buckets, locks, countPerLock); _defaultComparer = EqualityComparer<TKey>.Default; if (comparer != null && !ReferenceEquals(comparer, _defaultComparer) && // if this is the default comparer, take the optimized path !ReferenceEquals(comparer, StringComparer.Ordinal)) // strings as keys are extremely common, so special-case StringComparer.Ordinal, which is the same as the default comparer { _comparer = comparer; } _growLockArray = growLockArray; _budget = buckets.Length / locks.Length; } /// <summary> /// Attempts to add the specified key and value to the <see cref="ConcurrentDictionary2{TKey, TValue}"/>. /// </summary> /// <param name="key">The key of the element to add.</param> /// <param name="value">The value of the element to add. The value can be a null reference (Nothing /// in Visual Basic) for reference types.</param> /// <returns> /// true if the key/value pair was added to the <see cref="ConcurrentDictionary2{TKey, TValue}"/> successfully; otherwise, false. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is null reference (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The <see cref="ConcurrentDictionary2{TKey, TValue}"/> contains too many elements.</exception> public bool TryAdd(TKey key, TValue value) { if (key is null) { throw new ArgumentNullException(nameof(key)); } return TryAddInternal(key, null, value, updateIfExists: false, acquireLock: true, out _); } /// <summary> /// Determines whether the <see cref="ConcurrentDictionary2{TKey, TValue}"/> contains the specified key. /// </summary> /// <param name="key">The key to locate in the <see cref="ConcurrentDictionary2{TKey, TValue}"/>.</param> /// <returns>true if the <see cref="ConcurrentDictionary2{TKey, TValue}"/> contains an element with the specified key; otherwise, false.</returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception> public bool ContainsKey(TKey key) { if (key is null) { throw new ArgumentNullException(nameof(key)); } return TryGetValue(key, out _); } /// <summary> /// Attempts to remove and return the value with the specified key from the <see cref="ConcurrentDictionary2{TKey, TValue}"/>. /// </summary> /// <param name="key">The key of the element to remove and return.</param> /// <param name="value"> /// When this method returns, <paramref name="value"/> contains the object removed from the /// <see cref="ConcurrentDictionary2{TKey,TValue}"/> or the default value of <typeparamref /// name="TValue"/> if the operation failed. /// </param> /// <returns>true if an object was removed successfully; otherwise, false.</returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception> public bool TryRemove(TKey key, out TValue value) { if (key is null) { throw new ArgumentNullException(nameof(key)); } return TryRemoveInternal(key, out value, matchValue: false, default); } /// <summary>Removes a key and value from the dictionary.</summary> /// <param name="item">The <see cref="KeyValuePair{TKey,TValue}"/> representing the key and value to remove.</param> /// <returns> /// true if the key and value represented by <paramref name="item"/> are successfully /// found and removed; otherwise, false. /// </returns> /// <remarks> /// Both the specified key and value must match the entry in the dictionary for it to be removed. /// The key is compared using the dictionary's comparer (or the default comparer for <typeparamref name="TKey"/> /// if no comparer was provided to the dictionary when it was constructed). The value is compared using the /// default comparer for <typeparamref name="TValue"/>. /// </remarks> /// <exception cref="ArgumentNullException"> /// The <see cref="KeyValuePair{TKey, TValue}.Key"/> property of <paramref name="item"/> is a null reference. /// </exception> public bool TryRemove(KeyValuePair<TKey, TValue> item) { if (item.Key is null) { throw new ArgumentNullException(nameof(item), "ItemKeyIsNull"); } return TryRemoveInternal(item.Key, out _, matchValue: true, item.Value); } /// <summary> /// 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. /// </summary> /// <param name="key">The key to search for and remove if it exists.</param> /// <param name="value">The variable into which the removed value, if found, is stored.</param> /// <param name="matchValue">Whether removal of the key is conditional on its value.</param> /// <param name="oldValue">The conditional value to compare against if <paramref name="matchValue"/> is true</param> private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue? oldValue) { IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); while (true) { Tables tables = _tables; object[] locks = tables._locks; ref Node? bucket = ref tables.GetBucketAndLock(hashcode, out uint lockNo); lock (locks[lockNo]) { // If the table just got resized, we may not be holding the right lock, and must retry. // This should be a rare occurrence. if (tables != _tables) { continue; } Node? prev = null; for (Node? curr = bucket; curr != null; curr = curr._next) { Debug.Assert((prev is null && curr == bucket) || prev!._next == curr); if (hashcode == curr._hashcode && (comparer is null ? _defaultComparer.Equals(curr._key, key) : comparer.Equals(curr._key, key))) { if (matchValue) { bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue!, curr._value); if (!valuesMatch) { value = default; return false; } } if (prev is null) { Volatile.Write(ref bucket, curr._next); } else { prev._next = curr._next; } value = curr._value; tables._countPerLock[lockNo]--; return true; } prev = curr; } } value = default; return false; } } /// <summary> /// Attempts to get the value associated with the specified key from the <see cref="ConcurrentDictionary2{TKey,TValue}"/>. /// </summary> /// <param name="key">The key of the value to get.</param> /// <param name="value"> /// When this method returns, <paramref name="value"/> contains the object from /// the <see cref="ConcurrentDictionary2{TKey,TValue}"/> with the specified key or the default value of /// <typeparamref name="TValue"/>, if the operation failed. /// </param> /// <returns>true if the key was found in the <see cref="ConcurrentDictionary2{TKey,TValue}"/>; otherwise, false.</returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (Nothing in Visual Basic).</exception> public bool TryGetValue(TKey key, out TValue value) { if (key is null) { throw new ArgumentNullException(nameof(key)); } // We must capture the volatile _tables field into a local variable: it is set to a new table on each table resize. // The Volatile.Read on the array element then 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. Tables tables = _tables; IEqualityComparer<TKey>? comparer = _comparer; if (comparer is null) { int hashcode = key.GetHashCode(); if (typeof(TKey).IsValueType) { for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && EqualityComparer<TKey>.Default.Equals(n._key, key)) { value = n._value; return true; } } } else { for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && _defaultComparer.Equals(n._key, key)) { value = n._value; return true; } } } } else { int hashcode = comparer.GetHashCode(key); for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && comparer.Equals(n._key, key)) { value = n._value; return true; } } } value = default; return false; } private bool TryGetValueInternal(TKey key, int hashcode, out TValue value) { Debug.Assert((_comparer is null ? key.GetHashCode() : _comparer.GetHashCode(key)) == hashcode); // We must capture the volatile _tables field into a local variable: it is set to a new table on each table resize. // The Volatile.Read on the array element then 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. Tables tables = _tables; IEqualityComparer<TKey>? comparer = _comparer; if (comparer is null) { if (typeof(TKey).IsValueType) { for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && EqualityComparer<TKey>.Default.Equals(n._key, key)) { value = n._value; return true; } } } else { for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && _defaultComparer.Equals(n._key, key)) { value = n._value; return true; } } } } else { for (Node? n = Volatile.Read(ref tables.GetBucket(hashcode)); n != null; n = n._next) { if (hashcode == n._hashcode && comparer.Equals(n._key, key)) { value = n._value; return true; } } } value = default; return false; } /// <summary> /// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value is equal /// to <paramref name="comparisonValue"/>. /// </summary> /// <param name="key">The key whose value is compared with <paramref name="comparisonValue"/> and /// possibly replaced.</param> /// <param name="newValue">The value that replaces the value of the element with <paramref /// name="key"/> if the comparison results in equality.</param> /// <param name="comparisonValue">The value that is compared to the value of the element with /// <paramref name="key"/>.</param> /// <returns> /// true if the value with <paramref name="key"/> was equal to <paramref name="comparisonValue"/> and /// replaced with <paramref name="newValue"/>; otherwise, false. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference.</exception> public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) { if (key is null) { throw new ArgumentNullException(nameof(key)); } return TryUpdateInternal(key, null, newValue, comparisonValue); } /// <summary> /// Updates the value associated with <paramref name="key"/> to <paramref name="newValue"/> if the existing value is equal /// to <paramref name="comparisonValue"/>. /// </summary> /// <param name="key">The key whose value is compared with <paramref name="comparisonValue"/> and /// possibly replaced.</param> /// <param name="nullableHashcode">The hashcode computed for <paramref name="key"/>.</param> /// <param name="newValue">The value that replaces the value of the element with <paramref /// name="key"/> if the comparison results in equality.</param> /// <param name="comparisonValue">The value that is compared to the value of the element with /// <paramref name="key"/>.</param> /// <returns> /// true if the value with <paramref name="key"/> was equal to <paramref name="comparisonValue"/> and /// replaced with <paramref name="newValue"/>; otherwise, false. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference.</exception> private bool TryUpdateInternal(TKey key, int? nullableHashcode, TValue newValue, TValue comparisonValue) { IEqualityComparer<TKey>? comparer = _comparer; Debug.Assert( nullableHashcode is null || (comparer is null ? key.GetHashCode() : comparer.GetHashCode(key)) == nullableHashcode); int hashcode = nullableHashcode ?? (comparer is null ? key.GetHashCode() : comparer.GetHashCode(key)); EqualityComparer<TValue> valueComparer = EqualityComparer<TValue>.Default; while (true) { Tables tables = _tables; object[] locks = tables._locks; ref Node? bucket = ref tables.GetBucketAndLock(hashcode, out uint lockNo); lock (locks[lockNo]) { // If the table just got resized, we may not be holding the right lock, and must retry. // This should be a rare occurrence. if (tables != _tables) { continue; } // Try to find this key in the bucket Node? prev = null; for (Node? node = bucket; node != null; node = node._next) { Debug.Assert((prev is null && node == bucket) || prev!._next == node); if (hashcode == node._hashcode && (comparer is null ? _defaultComparer.Equals(node._key, key) : comparer.Equals(node._key, key))) { if (valueComparer.Equals(node._value, comparisonValue)) { if (s_isValueWriteAtomic) { node._value = newValue; } else { var newNode = new Node(node._key, newValue, hashcode, node._next); if (prev is null) { Volatile.Write(ref bucket, newNode); } else { prev._next = newNode; } } return true; } return false; } prev = node; } // didn't find the key return false; } } } /// <summary> /// Removes all keys and values from the <see cref="ConcurrentDictionary2{TKey,TValue}"/>. /// </summary> public void Clear() { int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); // If the dictionary is already empty, then there's nothing to clear. if (AreAllBucketsEmpty()) { return; } Tables tables = _tables; var 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 { ReleaseLocks(0, locksAcquired); } } /// <summary> /// Copies the elements of the <see cref="ICollection{T}"/> to an array of type <see cref="KeyValuePair{TKey,TValue}"/>, /// starting at the specified array index. /// </summary> /// <param name="array"> /// The one-dimensional array of type <see cref="KeyValuePair{TKey,TValue}"/> that is the destination of the <see /// cref="KeyValuePair{TKey,TValue}"/> elements copied from the <see cref="ICollection"/>. The array must have zero-based indexing. /// </param> /// <param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param> /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.</exception> /// <exception cref="ArgumentException"> /// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/>. -or- The number of /// elements in the source <see cref="ICollection"/> is greater than the available space from <paramref name="index"/> to /// the end of the destination <paramref name="array"/>. /// </exception> void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index) { if (array is null) { throw new ArgumentNullException(nameof(array)); } if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index), "IndexIsNegative"); } int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); int count = 0; int[] countPerLock = _tables._countPerLock; for (int i = 0; i < countPerLock.Length && count >= 0; i++) { count += countPerLock[i]; } if (array.Length — count < index || count < 0) //"count" itself or "count + index" can overflow { throw new ArgumentException("ArrayNotLargeEnough"); } CopyToPairs(array, index); } finally { ReleaseLocks(0, locksAcquired); } } /// <summary> /// Copies the key and value pairs stored in the <see cref="ConcurrentDictionary2{TKey,TValue}"/> to a /// new array. /// </summary> /// <returns>A new array containing a snapshot of key and value pairs copied from the <see cref="ConcurrentDictionary2{TKey,TValue}"/>. /// </returns> public KeyValuePair<TKey, TValue>[] ToArray() { int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); int count = 0; int[] countPerLock = _tables._countPerLock; for (int i = 0; i < countPerLock.Length; i++) { checked { count += countPerLock[i]; } } if (count == 0) { return new KeyValuePair<TKey, TValue>[0]; } var array = new KeyValuePair<TKey, TValue>[count]; CopyToPairs(array, 0); return array; } finally { ReleaseLocks(0, locksAcquired); } } /// <summary>Copy dictionary contents to an array — shared implementation between ToArray and CopyTo.</summary> /// <remarks>Important: the caller must hold all locks in _locks before calling CopyToPairs.</remarks> private void CopyToPairs(KeyValuePair<TKey, TValue>[] array, int index) { Node?[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { for (Node? current = buckets[i]; current != null; current = current._next) { array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value); index++; // this should never overflow, CopyToPairs is only called when there's no overflow risk } } } /// <summary>Copy dictionary contents to an array — shared implementation between ToArray and CopyTo.</summary> /// <remarks>Important: the caller must hold all locks in _locks before calling CopyToPairs.</remarks> 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._next) { array[index] = new DictionaryEntry(current._key, current._value); index++; //this should never flow, CopyToEntries is only called when there's no overflow risk } } } /// <summary>Copy dictionary contents to an array — shared implementation between ToArray and CopyTo.</summary> /// <remarks>Important: the caller must hold all locks in _locks before calling CopyToPairs.</remarks> 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._next) { array[index] = new KeyValuePair<TKey, TValue>(current._key, current._value); index++; // this should never overflow, CopyToObjects is only called when there's no overflow risk } } } /// <summary>Returns an enumerator that iterates through the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</summary> /// <returns>An enumerator for the <see cref="ConcurrentDictionary2{TKey,TValue}"/>.</returns> /// <remarks> /// 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 <see cref="GetEnumerator"/> was called. /// </remarks> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator(this); /// <summary>Provides an enumerator implementation for the dictionary.</summary> private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> { // Provides a manually-implemented version of (approximately) this iterator: // Node?[] buckets = _tables._buckets; // for (int i = 0; i < buckets.Length; i++) // for (Node? current = Volatile.Read(ref buckets[i]); current != null; current = current._next) // yield return new KeyValuePair<TKey, TValue>(current._key, current._value); private readonly ConcurrentDictionary2<TKey, TValue> _dictionary; private ConcurrentDictionary2<TKey, TValue>.Node?[]? _buckets; private Node? _node; private int _i; private int _state; private const int StateUninitialized = 0; private const int StateOuterloop = 1; private const int StateInnerLoop = 2; private const int StateDone = 3; public Enumerator(ConcurrentDictionary2<TKey, TValue> dictionary) { _dictionary = dictionary; _i = -1; } public KeyValuePair<TKey, TValue> Current { get; private set; } object IEnumerator.Current => Current; public void Reset() { _buckets = null; _node = null; Current = default; _i = -1; _state = StateUninitialized; } public void Dispose() { } public bool MoveNext() { switch (_state) { case StateUninitialized: _buckets = _dictionary._tables._buckets; _i = -1; goto case StateOuterloop; case StateOuterloop: ConcurrentDictionary2<TKey, TValue>.Node?[]? buckets = _buckets; Debug.Assert(buckets != null); int i = ++_i; if ((uint)i < (uint)buckets!.Length) { // 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 = Volatile.Read(ref buckets[i]); _state = StateInnerLoop; goto case StateInnerLoop; } goto default; case StateInnerLoop: Node? node = _node; if (node != null) { Current = new KeyValuePair<TKey, TValue>(node._key, node._value); _node = node._next; return true; } goto case StateOuterloop; default: _state = StateDone; return false; } } } /// <summary> /// 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; /// </summary> private bool TryAddInternal(TKey key, int? nullableHashcode, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue) { IEqualityComparer<TKey>? comparer = _comparer; Debug.Assert( nullableHashcode is null || (comparer is null && key.GetHashCode() == nullableHashcode) || (comparer != null && comparer.GetHashCode(key) == nullableHashcode)); int hashcode = nullableHashcode ?? (comparer is null ? key.GetHashCode() : comparer.GetHashCode(key)); while (true) { Tables tables = _tables; object[] locks = tables._locks; ref Node? bucket = ref tables.GetBucketAndLock(hashcode, out uint lockNo); bool resizeDesired = false; bool lockTaken = false; try { if (acquireLock) { Monitor.Enter(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 occurrence. if (tables != _tables) { continue; } // Try to find this key in the bucket Node? prev = null; for (Node? node = bucket; node != null; node = node._next) { Debug.Assert((prev is null && node == bucket) || prev!._next == node); if (hashcode == node._hashcode && (comparer is null ? _defaultComparer.Equals(node._key, key) : 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 // be written atomically, since lock-free reads may be happening concurrently. if (updateIfExists) { if (s_isValueWriteAtomic) { node._value = value; } else { var newNode = new Node(node._key, value, hashcode, node._next); if (prev is null) { Volatile.Write(ref bucket, newNode); } else { prev._next = newNode; } } resultingValue = value; } else { resultingValue = node._value; } return false; } prev = node; } // The key was not found in the bucket. Insert the key-value pair. var resultNode = new Node(key, value, hashcode, bucket); Volatile.Write(ref bucket, resultNode); 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(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); } resultingValue = value; return true; } } /// <summary>Gets or sets the value associated with the specified key.</summary> /// <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="KeyNotFoundException"/>, and a set operation creates a new element with the specified key. /// </value> /// <exception cref="ArgumentNullException"> /// <paramref name="key"/> is a null reference (Nothing in Visual Basic). /// </exception> /// <exception cref="KeyNotFoundException"> /// The property is retrieved and <paramref name="key"/> does not exist in the collection. /// </exception> public TValue this[TKey key] { get { if (!TryGetValue(key, out TValue? value)) { ThrowKeyNotFoundException(key); } return value; } set { if (key is null) { throw new ArgumentNullException(nameof(key)); } TryAddInternal(key, null, value, updateIfExists: true, acquireLock: true, out _); } } /// <summary>Throws a KeyNotFoundException.</summary> /// <remarks>Separate from ThrowHelper to avoid boxing at call site while reusing this generic instantiation.</remarks> private static void ThrowKeyNotFoundException(TKey key) => throw new KeyNotFoundException(key.ToString()); /// <summary> /// Gets the number of key/value pairs contained in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>. /// </summary> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <value>The number of key/value pairs contained in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</value> /// <remarks>Count has snapshot semantics and represents the number of items in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/> /// at the moment when Count was accessed.</remarks> public int Count { get { int acquiredLocks = 0; try { // Acquire all locks AcquireAllLocks(ref acquiredLocks); return GetCountInternal(); } finally { // Release locks that have been acquired earlier ReleaseLocks(0, acquiredLocks); } } } /// <summary> /// Gets the number of key/value pairs contained in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>. Should only be used after all locks /// have been acquired. /// </summary> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <value>The number of key/value pairs contained in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</value> /// <remarks>Count has snapshot semantics and represents the number of items in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/> /// at the moment when Count was accessed.</remarks> private int GetCountInternal() { int count = 0; int[] countPerLocks = _tables._countPerLock; // Compute the count, we allow overflow for (int i = 0; i < countPerLocks.Length; i++) { count += countPerLocks[i]; } return count; } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// if the key does not already exist. /// </summary> /// <param name="key">The key of the element to add.</param> /// <param name="valueFactory">The function used to generate a value for the key</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="valueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>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.</returns> public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (valueFactory is null) { throw new ArgumentNullException(nameof(valueFactory)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue)) { TryAddInternal(key, hashcode, valueFactory(key), updateIfExists: false, acquireLock: true, out resultingValue); } return resultingValue; } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// if the key does not already exist. /// </summary> /// <param name="key">The key of the element to add.</param> /// <param name="valueFactory">The function used to generate a value for the key</param> /// <param name="factoryArgument">An argument value to pass into <paramref name="valueFactory"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="valueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>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.</returns> public TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (valueFactory is null) { throw new ArgumentNullException(nameof(valueFactory)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue)) { TryAddInternal(key, hashcode, valueFactory(key, factoryArgument), updateIfExists: false, acquireLock: true, out resultingValue); } return resultingValue; } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> /// if the key does not already exist. /// </summary> /// <param name="key">The key of the element to add.</param> /// <param name="value">the value to be added, if the key does not already exist</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>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.</returns> public TValue GetOrAdd(TKey key, TValue value) { if (key is null) { throw new ArgumentNullException(nameof(key)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue)) { TryAddInternal(key, hashcode, value, updateIfExists: false, acquireLock: true, out resultingValue); } return resultingValue; } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key does not already /// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key /// already exists. /// </summary> /// <param name="key">The key to be added or whose value should be updated</param> /// <param name="addValueFactory">The function used to generate a value for an absent key</param> /// <param name="updateValueFactory">The function used to generate a new value for an existing key /// based on the key's existing value</param> /// <param name="factoryArgument">An argument to pass into <paramref name="addValueFactory"/> and <paramref name="updateValueFactory"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="addValueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>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).</returns> public TValue AddOrUpdate<TArg>( TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (addValueFactory is null) { throw new ArgumentNullException(nameof(addValueFactory)); } if (updateValueFactory is null) { throw new ArgumentNullException(nameof(updateValueFactory)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); while (true) { if (TryGetValueInternal(key, hashcode, out TValue? oldValue)) { // key exists, try to update TValue newValue = updateValueFactory(key, oldValue, factoryArgument); if (TryUpdateInternal(key, hashcode, newValue, oldValue)) { return newValue; } } else { // key doesn't exist, try to add if (TryAddInternal(key, hashcode, addValueFactory(key, factoryArgument), updateIfExists: false, acquireLock: true, out TValue resultingValue)) { return resultingValue; } } } } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key does not already /// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key /// already exists. /// </summary> /// <param name="key">The key to be added or whose value should be updated</param> /// <param name="addValueFactory">The function used to generate a value for an absent key</param> /// <param name="updateValueFactory">The function used to generate a new value for an existing key /// based on the key's existing value</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="addValueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>The new value for the key. This will be either the result of addValueFactory (if the key was /// absent) or the result of updateValueFactory (if the key was present).</returns> public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (addValueFactory is null) { throw new ArgumentNullException(nameof(addValueFactory)); } if (updateValueFactory is null) { throw new ArgumentNullException(nameof(updateValueFactory)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); while (true) { if (TryGetValueInternal(key, hashcode, out TValue? oldValue)) { // key exists, try to update TValue newValue = updateValueFactory(key, oldValue); if (TryUpdateInternal(key, hashcode, newValue, oldValue)) { return newValue; } } else { // key doesn't exist, try to add if (TryAddInternal(key, hashcode, addValueFactory(key), updateIfExists: false, acquireLock: true, out TValue resultingValue)) { return resultingValue; } } } } /// <summary> /// Adds a key/value pair to the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key does not already /// exist, or updates a key/value pair in the <see cref="ConcurrentDictionary2{TKey,TValue}"/> if the key /// already exists. /// </summary> /// <param name="key">The key to be added or whose value should be updated</param> /// <param name="addValue">The value to be added for an absent key</param> /// <param name="updateValueFactory">The function used to generate a new value for an existing key based on /// the key's existing value</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentNullException"><paramref name="updateValueFactory"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <returns>The new value for the key. This will be either the value of addValue (if the key was /// absent) or the result of updateValueFactory (if the key was present).</returns> public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (updateValueFactory is null) { throw new ArgumentNullException(nameof(updateValueFactory)); } IEqualityComparer<TKey>? comparer = _comparer; int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key); while (true) { if (TryGetValueInternal(key, hashcode, out TValue? oldValue)) { // key exists, try to update TValue newValue = updateValueFactory(key, oldValue); if (TryUpdateInternal(key, hashcode, newValue, oldValue)) { return newValue; } } else { // key doesn't exist, try to add if (TryAddInternal(key, hashcode, addValue, updateIfExists: false, acquireLock: true, out TValue resultingValue)) { return resultingValue; } } } } /// <summary> /// Gets a value that indicates whether the <see cref="ConcurrentDictionary2{TKey,TValue}"/> is empty. /// </summary> /// <value>true if the <see cref="ConcurrentDictionary2{TKey,TValue}"/> is empty; otherwise, /// false.</value> public bool IsEmpty { get { // Check if any buckets are non-empty, without acquiring any locks. // This fast path should generally suffice as collections are usually not empty. if (!AreAllBucketsEmpty()) { return false; } // We didn't see any buckets containing items, however we can't be sure // the collection was actually empty at any point in time as items may have been // added and removed while iterating over the buckets such that we never saw an // empty bucket, but there was always an item present in at least one bucket. int acquiredLocks = 0; try { // Acquire all locks AcquireAllLocks(ref acquiredLocks); return AreAllBucketsEmpty(); } finally { // Release locks that have been acquired earlier ReleaseLocks(0, acquiredLocks); } } } #region IDictionary<TKey,TValue> members /// <summary> /// Adds the specified key and value to the <see /// cref="IDictionary{TKey,TValue}"/>. /// </summary> /// <param name="key">The object to use as the key of the element to add.</param> /// <param name="value">The object to use as the value of the element to add.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <exception cref="ArgumentException"> /// An element with the same key already exists in the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</exception> void IDictionary<TKey, TValue>.Add(TKey key, TValue value) { if (!TryAdd(key, value)) { throw new ArgumentException("KeyAlreadyExisted"); } } /// <summary> /// Removes the element with the specified key from the <see /// cref="IDictionary{TKey,TValue}"/>. /// </summary> /// <param name="key">The key of the element to remove.</param> /// <returns>true if the element is successfully remove; otherwise false. This method also returns /// false if /// <paramref name="key"/> was not found in the original <see /// cref="IDictionary{TKey,TValue}"/>. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> bool IDictionary<TKey, TValue>.Remove(TKey key) => TryRemove(key, out _); /// <summary> /// Gets a collection containing the keys in the <see /// cref="Dictionary{TKey,TValue}"/>. /// </summary> /// <value>An <see cref="ICollection{TKey}"/> containing the keys in the /// <see cref="Dictionary{TKey,TValue}"/>.</value> public ICollection<TKey> Keys => GetKeys(); /// <summary> /// Gets an <see cref="IEnumerable{TKey}"/> containing the keys of /// the <see cref="IReadOnlyDictionary{TKey,TValue}"/>. /// </summary> /// <value>An <see cref="IEnumerable{TKey}"/> containing the keys of /// the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.</value> //IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => GetKeys(); /// <summary> /// Gets a collection containing the values in the <see /// cref="Dictionary{TKey,TValue}"/>. /// </summary> /// <value>An <see cref="ICollection{TValue}"/> containing the values in /// the /// <see cref="Dictionary{TKey,TValue}"/>.</value> public ICollection<TValue> Values => GetValues(); /// <summary> /// Gets an <see cref="IEnumerable{TValue}"/> containing the values /// in the <see cref="IReadOnlyDictionary{TKey,TValue}"/>. /// </summary> /// <value>An <see cref="IEnumerable{TValue}"/> containing the /// values in the <see cref="IReadOnlyDictionary{TKey,TValue}"/>.</value> //IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => GetValues(); #endregion #region ICollection<KeyValuePair<TKey,TValue>> Members /// <summary> /// Adds the specified value to the <see cref="ICollection{TValue}"/> /// with the specified key. /// </summary> /// <param name="keyValuePair">The <see cref="KeyValuePair{TKey,TValue}"/> /// structure representing the key and value to add to the <see /// cref="Dictionary{TKey,TValue}"/>.</param> /// <exception cref="ArgumentNullException">The <paramref name="keyValuePair"/> of <paramref /// name="keyValuePair"/> is null.</exception> /// <exception cref="OverflowException">The <see /// cref="Dictionary{TKey,TValue}"/> /// contains too many elements.</exception> /// <exception cref="ArgumentException">An element with the same key already exists in the /// <see cref="Dictionary{TKey,TValue}"/></exception> void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair) => ((IDictionary<TKey, TValue>)this).Add(keyValuePair.Key, keyValuePair.Value); /// <summary> /// Determines whether the <see cref="ICollection{T}"/> /// contains a specific key and value. /// </summary> /// <param name="keyValuePair">The <see cref="KeyValuePair{TKey,TValue}"/> /// structure to locate in the <see /// cref="ICollection{TValue}"/>.</param> /// <returns>true if the <paramref name="keyValuePair"/> is found in the <see /// cref="ICollection{T}"/>; otherwise, false.</returns> bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair) { if (!TryGetValue(keyValuePair.Key, out TValue? value)) { return false; } return EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value); } /// <summary> /// Gets a value indicating whether the dictionary is read-only. /// </summary> /// <value>true if the <see cref="ICollection{T}"/> is /// read-only; otherwise, false. For <see /// cref="Dictionary{TKey,TValue}"/>, this property always returns /// false.</value> bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; /// <summary> /// Removes a key and value from the dictionary. /// </summary> /// <param name="keyValuePair">The <see /// cref="KeyValuePair{TKey,TValue}"/> /// structure representing the key and value to remove from the <see /// cref="Dictionary{TKey,TValue}"/>.</param> /// <returns>true if the key and value represented by <paramref name="keyValuePair"/> is successfully /// found and removed; otherwise, false.</returns> /// <exception cref="ArgumentNullException">The Key property of <paramref /// name="keyValuePair"/> is a null reference (Nothing in Visual Basic).</exception> bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair) => TryRemove(keyValuePair); #endregion #region IEnumerable Members /// <summary>Returns an enumerator that iterates through the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</summary> /// <returns>An enumerator for the <see cref="ConcurrentDictionary2{TKey,TValue}"/>.</returns> /// <remarks> /// 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 <see cref="GetEnumerator"/> was called. /// </remarks> IEnumerator IEnumerable.GetEnumerator() => ((ConcurrentDictionary2<TKey, TValue>)this).GetEnumerator(); #endregion #region IDictionary Members /// <summary> /// Adds the specified key and value to the dictionary. /// </summary> /// <param name="key">The object to use as the key.</param> /// <param name="value">The object to use as the value.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="OverflowException">The dictionary contains too many /// elements.</exception> /// <exception cref="ArgumentException"> /// <paramref name="key"/> is of a type that is not assignable to the key type <typeparamref /// name="TKey"/> of the <see cref="Dictionary{TKey,TValue}"/>. -or- /// <paramref name="value"/> is of a type that is not assignable to <typeparamref name="TValue"/>, /// the type of values in the <see cref="Dictionary{TKey,TValue}"/>. /// -or- A value with the same key already exists in the <see /// cref="Dictionary{TKey,TValue}"/>. /// </exception> void IDictionary.Add(object key, object? value) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (!(key is TKey)) { throw new ArgumentException("TypeOfKeyIncorrect"); } ThrowIfInvalidObjectValue(value); ((IDictionary<TKey, TValue>)this).Add((TKey)key, (TValue)value!); } /// <summary> /// Gets whether the <see cref="IDictionary"/> contains an /// element with the specified key. /// </summary> /// <param name="key">The key to locate in the <see /// cref="IDictionary"/>.</param> /// <returns>true if the <see cref="IDictionary"/> contains /// an element with the specified key; otherwise, false.</returns> /// <exception cref="ArgumentNullException"> <paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> bool IDictionary.Contains(object key) { if (key is null) { throw new ArgumentNullException(nameof(key)); } return key is TKey tkey && ContainsKey(tkey); } /// <summary>Provides an <see cref="IDictionaryEnumerator"/> for the /// <see cref="IDictionary"/>.</summary> /// <returns>An <see cref="IDictionaryEnumerator"/> for the <see /// cref="IDictionary"/>.</returns> IDictionaryEnumerator IDictionary.GetEnumerator() => new DictionaryEnumerator(this); /// <summary> /// Gets a value indicating whether the <see /// cref="IDictionary"/> has a fixed size. /// </summary> /// <value>true if the <see cref="IDictionary"/> has a /// fixed size; otherwise, false. For <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>, this property always /// returns false.</value> bool IDictionary.IsFixedSize => false; /// <summary> /// Gets a value indicating whether the <see /// cref="IDictionary"/> is read-only. /// </summary> /// <value>true if the <see cref="IDictionary"/> is /// read-only; otherwise, false. For <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>, this property always /// returns false.</value> bool IDictionary.IsReadOnly => false; /// <summary> /// Gets an <see cref="ICollection"/> containing the keys of the <see /// cref="IDictionary"/>. /// </summary> /// <value>An <see cref="ICollection"/> containing the keys of the <see /// cref="IDictionary"/>.</value> ICollection IDictionary.Keys => GetKeys(); /// <summary> /// Removes the element with the specified key from the <see /// cref="IDictionary"/>. /// </summary> /// <param name="key">The key of the element to remove.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> void IDictionary.Remove(object key) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (key is TKey tkey) { TryRemove(tkey, out _); } } /// <summary> /// Gets an <see cref="ICollection"/> containing the values in the <see /// cref="IDictionary"/>. /// </summary> /// <value>An <see cref="ICollection"/> containing the values in the <see /// cref="IDictionary"/>.</value> ICollection IDictionary.Values => GetValues(); /// <summary> /// Gets or sets the value associated with the specified key. /// </summary> /// <param name="key">The key of the value to get or set.</param> /// <value>The value associated with the specified key, or a null reference (Nothing in Visual Basic) /// if <paramref name="key"/> is not in the dictionary or <paramref name="key"/> is of a type that is /// not assignable to the key type <typeparamref name="TKey"/> of the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>.</value> /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentException"> /// A value is being assigned, and <paramref name="key"/> is of a type that is not assignable to the /// key type <typeparamref name="TKey"/> of the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>. -or- A value is being /// assigned, and <paramref name="key"/> is of a type that is not assignable to the value type /// <typeparamref name="TValue"/> of the <see /// cref="ConcurrentDictionary2{TKey,TValue}"/> /// </exception> object? IDictionary.this[object key] { get { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (key is TKey tkey && TryGetValue(tkey, out TValue? value)) { return value; } return null; } set { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (!(key is TKey)) { throw new ArgumentException("TypeOfKeyIncorrect"); } ThrowIfInvalidObjectValue(value); ((ConcurrentDictionary2<TKey, TValue>)this)[(TKey)key] = (TValue)value!; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ThrowIfInvalidObjectValue(object? value) { if (value != null) { if (!(value is TValue)) { throw new ArgumentException("Type of value is incorrect"); } } else if (default(TValue) != null) { throw new ArgumentException("Type of value is incorrect"); } } #endregion #region ICollection Members /// <summary> /// Copies the elements of the <see cref="ICollection"/> to an array, starting /// at the specified array index. /// </summary> /// <param name="array">The one-dimensional array that is the destination of the elements copied from /// the <see cref="ICollection"/>. The array must have zero-based /// indexing.</param> /// <param name="index">The zero-based index in <paramref name="array"/> at which copying /// begins.</param> /// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference /// (Nothing in Visual Basic).</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than /// 0.</exception> /// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than /// the length of the <paramref name="array"/>. -or- The number of elements in the source <see /// cref="ICollection"/> /// is greater than the available space from <paramref name="index"/> to the end of the destination /// <paramref name="array"/>.</exception> void ICollection.CopyTo(Array array, int index) { if (array is null) { throw new ArgumentNullException(nameof(array)); } if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index), "IndexIsNegative"); } int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); Tables tables = _tables; int count = 0; int[] countPerLock = tables._countPerLock; for (int i = 0; i < countPerLock.Length && count >= 0; i++) { count += countPerLock[i]; } if (array.Length — count < index || count < 0) //"count" itself or "count + index" can overflow { throw new ArgumentException("ArrayNotLargeEnough"); } // To be consistent with the behavior of ICollection.CopyTo() in Dictionary<TKey,TValue>, // we recognize three types of target arrays: // — an array of KeyValuePair<TKey, TValue> structs // — an array of DictionaryEntry structs // — an array of objects if (array is KeyValuePair<TKey, TValue>[] pairs) { CopyToPairs(pairs, index); return; } if (array is DictionaryEntry[] entries) { CopyToEntries(entries, index); return; } if (array is object[] objects) { CopyToObjects(objects, index); return; } throw new ArgumentException("ArrayIncorrectType", nameof(array)); } finally { ReleaseLocks(0, locksAcquired); } } /// <summary> /// Gets a value indicating whether access to the <see cref="ICollection"/> is /// synchronized with the SyncRoot. /// </summary> /// <value>true if access to the <see cref="ICollection"/> is synchronized /// (thread safe); otherwise, false. For <see /// cref="ConcurrentDictionary2{TKey,TValue}"/>, this property always /// returns false.</value> bool ICollection.IsSynchronized => false; /// <summary> /// Gets an object that can be used to synchronize access to the <see /// cref="ICollection"/>. This property is not supported. /// </summary> /// <exception cref="NotSupportedException">The SyncRoot property is not supported.</exception> object ICollection.SyncRoot => throw new NotSupportedException("SyncRoot_NotSupported"); #endregion private bool AreAllBucketsEmpty() { int[] countPerLock = _tables._countPerLock; for (int i = 0; i < countPerLock.Length; i++) { if (countPerLock[i] != 0) { return false; } } return true; } /// <summary> /// Replaces the bucket table with a larger one. To prevent multiple threads from resizing the /// 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. /// </summary> private void GrowTable(Tables tables) { const int MaxArrayLength = 0X7FEFFFFF; int locksAcquired = 0; try { // The thread that first obtains _locks[0] will be the one doing the resize operation AcquireLocks(0, 1, ref locksAcquired); // Make sure nobody resized the table while we were waiting for lock 0: 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; } Debug.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 ConcurrentDictionary2 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 < MaxLockNumber) { 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(); } } var newBuckets = new Node[newLength]; var newCountPerLock = new int[newLocks.Length]; var newTables = new Tables(newBuckets, newLocks, newCountPerLock); // Copy all data into a new table, creating new nodes for all elements foreach (Node? bucket in tables._buckets) { Node? current = bucket; while (current != null) { Node? next = current._next; ref Node? newBucket = ref newTables.GetBucketAndLock(current._hashcode, out uint newLockNo); newBucket = new Node(current._key, current._value, current._hashcode, newBucket); checked { newCountPerLock[newLockNo]++; } current = next; } } // Adjust the budget _budget = Math.Max(1, newBuckets.Length / newLocks.Length); // Replace tables with the new versions _tables = newTables; } finally { // Release all locks that we took earlier ReleaseLocks(0, locksAcquired); } } /// <summary>The number of concurrent writes for which to optimize by default.</summary> private static int DefaultConcurrencyLevel => Environment.ProcessorCount; /// <summary> /// 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. /// </summary> private void AcquireAllLocks(ref int locksAcquired) { // First, acquire lock 0 AcquireLocks(0, 1, ref locksAcquired); // 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> /// 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. /// </summary> private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) { Debug.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++; } } } } /// <summary> /// Releases a contiguous range of locks. /// </summary> private void ReleaseLocks(int fromInclusive, int toExclusive) { Debug.Assert(fromInclusive <= toExclusive); Tables tables = _tables; for (int i = fromInclusive; i < toExclusive; i++) { Monitor.Exit(tables._locks[i]); } } /// <summary> /// Gets a collection containing the keys in the dictionary. /// </summary> private ReadOnlyCollection<TKey> GetKeys() { int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); int count = GetCountInternal(); if (count < 0) { throw new OutOfMemoryException(); } var keys = new List<TKey>(count); Node?[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { for (Node? current = buckets[i]; current != null; current = current._next) { keys.Add(current._key); } } return new ReadOnlyCollection<TKey>(keys); } finally { ReleaseLocks(0, locksAcquired); } } /// <summary> /// Gets a collection containing the values in the dictionary. /// </summary> private ReadOnlyCollection<TValue> GetValues() { int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); int count = GetCountInternal(); if (count < 0) { throw new OutOfMemoryException(); } var values = new List<TValue>(count); Node?[] buckets = _tables._buckets; for (int i = 0; i < buckets.Length; i++) { for (Node? current = buckets[i]; current != null; current = current._next) { values.Add(current._value); } } return new ReadOnlyCollection<TValue>(values); } finally { ReleaseLocks(0, locksAcquired); } } /// <summary> /// A node in a singly-linked list representing a particular hash table bucket. /// </summary> private sealed class Node { 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) { _key = key; _value = value; _next = next; _hashcode = hashcode; } } /// <summary>Tables that hold the internal state of the ConcurrentDictionary2</summary> /// <remarks> /// Wrapping the three tables in a single object allows us to atomically /// replace all tables at once. /// </remarks> private sealed class Tables { /// <summary>A singly-linked list for each bucket.</summary> internal readonly Node?[] _buckets; /// <summary>A set of locks, each guarding a section of the table.</summary> internal readonly object[] _locks; /// <summary>The number of elements guarded by each lock.</summary> internal readonly int[] _countPerLock; /// <summary>Pre-computed multiplier for use on 64-bit performing faster modulo operations.</summary> internal readonly ulong _fastModBucketsMultiplier; internal Tables(Node?[] buckets, object[] locks, int[] countPerLock) { _buckets = buckets; _locks = locks; _countPerLock = countPerLock; if (IntPtr.Size == 8) { _fastModBucketsMultiplier = HashHelpers.GetFastModMultiplier((uint)buckets.Length); } } /// <summary>Computes a ref to the bucket for a particular key.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref Node? GetBucket(int hashcode) { Node?[] buckets = _buckets; if (IntPtr.Size == 8) { return ref buckets[HashHelpers.FastMod((uint)hashcode, (uint)buckets.Length, _fastModBucketsMultiplier)]; } else { return ref buckets[(uint)hashcode % (uint)buckets.Length]; } } /// <summary>Computes the bucket and lock number for a particular key.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref Node? GetBucketAndLock(int hashcode, out uint lockNo) { Node?[] buckets = _buckets; uint bucketNo; if (IntPtr.Size == 8) { bucketNo = HashHelpers.FastMod((uint)hashcode, (uint)buckets.Length, _fastModBucketsMultiplier); } else { bucketNo = (uint)hashcode % (uint)buckets.Length; } lockNo = bucketNo % (uint)_locks.Length; // doesn't use FastMod, as it would require maintaining a different multiplier return ref buckets[bucketNo]; } } /// <summary> /// A private class to represent enumeration over the dictionary that implements the /// IDictionaryEnumerator interface. /// </summary> private sealed class DictionaryEnumerator : IDictionaryEnumerator { private readonly IEnumerator<KeyValuePair<TKey, TValue>> _enumerator; // Enumerator over the dictionary. internal DictionaryEnumerator(ConcurrentDictionary2<TKey, TValue> dictionary) => _enumerator = dictionary.GetEnumerator(); public DictionaryEntry Entry => new DictionaryEntry(_enumerator.Current.Key, _enumerator.Current.Value); public object Key => _enumerator.Current.Key; public object? Value => _enumerator.Current.Value; public object Current => Entry; public bool MoveNext() => _enumerator.MoveNext(); public void Reset() => _enumerator.Reset(); } } | |
Interlocked.CompareExchange против Volatile.Read
Почему код работает корректно,
а код, который даже более строгий, не выполняется как надо
Пример кода: https://dotnetfiddle.net/WPb7K3
public static T Read<T>(ref T location) where T : class?
{
// return Volatile.Read(ref location);
// Volatile.Read implementation
T obj = location;
Thread.MemoryBarrier();
return obj;
}
а код, который даже более строгий, не выполняется как надо
public static T Read<T>(ref T location) where T : class?
{
return Interlocked.CompareExchange(ref location, location, location);
}
Пример кода: https://dotnetfiddle.net/WPb7K3
Скрытый текст | |
| |