diff --git a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs index 7f63ffb0f8b209..c72009696cef15 100644 --- a/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs +++ b/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs @@ -177,11 +177,10 @@ internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockA } capacity = HashHelpers.GetPrime(capacity); - 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++) + var locks = new Lock[concurrencyLevel]; + for (int i = 0; i < locks.Length; i++) { - locks[i] = new object(); + locks[i] = new Lock(); } var countPerLock = new int[locks.Length]; @@ -440,7 +439,7 @@ private bool TryRemoveInternal(TKey key, [MaybeNullWhen(false)] out TValue value while (true) { - object[] locks = tables._locks; + Lock[] locks = tables._locks; ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo); // Do a hot read on number of items stored in the bucket. If it's empty, we can avoid @@ -640,7 +639,7 @@ private bool TryUpdateInternal(Tables tables, TKey key, int? nullableHashcode, T while (true) { - object[] locks = tables._locks; + Lock[] locks = tables._locks; ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo); lock (locks[lockNo]) @@ -951,19 +950,13 @@ private bool TryAddInternal(Tables tables, TKey key, int? nullableHashcode, TVal while (true) { - object[] locks = tables._locks; + Lock[] locks = tables._locks; ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo); bool resizeDesired = false; bool forceRehash = false; - bool lockTaken = false; - try + using (acquireLock ? locks[lockNo].EnterScope() : default) { - 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) @@ -1050,13 +1043,6 @@ private bool TryAddInternal(Tables tables, TKey key, int? nullableHashcode, TVal forceRehash = 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. // @@ -2020,16 +2006,16 @@ private void GrowTable(Tables tables, bool resizeDesired, bool forceRehashIfNonR } } - object[] newLocks = tables._locks; + Lock[] newLocks = tables._locks; // Add more locks if (_growLockArray && tables._locks.Length < MaxLockNumber) { - newLocks = new object[tables._locks.Length * 2]; + newLocks = new Lock[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(); + newLocks[i] = new Lock(); } } @@ -2104,11 +2090,10 @@ private void AcquireAllLocks(ref int locksAcquired) /// private void AcquireFirstLock(ref int locksAcquired) { - object[] locks = _tables._locks; + Lock[] locks = _tables._locks; Debug.Assert(locksAcquired == 0); - Debug.Assert(!Monitor.IsEntered(locks[0])); - Monitor.Enter(locks[0]); + locks[0].Enter(); locksAcquired = 1; } @@ -2121,13 +2106,12 @@ private void AcquireFirstLock(ref int locksAcquired) /// private static void AcquirePostFirstLock(Tables tables, ref int locksAcquired) { - object[] locks = tables._locks; - Debug.Assert(Monitor.IsEntered(locks[0])); + Lock[] locks = tables._locks; Debug.Assert(locksAcquired == 1); for (int i = 1; i < locks.Length; i++) { - Monitor.Enter(locks[i]); + locks[i].Enter(); locksAcquired++; } @@ -2140,10 +2124,10 @@ private void ReleaseLocks(int locksAcquired) { Debug.Assert(locksAcquired >= 0); - object[] locks = _tables._locks; + Lock[] locks = _tables._locks; for (int i = 0; i < locksAcquired; i++) { - Monitor.Exit(locks[i]); + locks[i].Exit(); } } @@ -2293,11 +2277,11 @@ private sealed class Tables /// Pre-computed multiplier for use on 64-bit performing faster modulo operations. internal readonly ulong _fastModBucketsMultiplier; /// A set of locks, each guarding a section of the table. - internal readonly object[] _locks; + internal readonly Lock[] _locks; /// The number of elements guarded by each lock. internal readonly int[] _countPerLock; - internal Tables(VolatileNode[] buckets, object[] locks, int[] countPerLock, IEqualityComparer? comparer) + internal Tables(VolatileNode[] buckets, Lock[] locks, int[] countPerLock, IEqualityComparer? comparer) { Debug.Assert(typeof(TKey).IsValueType || comparer is not null); @@ -2419,16 +2403,13 @@ private bool TryAdd(TAlternateKey key, TValue value, bool updateIfExists, out TV while (true) { - object[] locks = tables._locks; + Lock[] locks = tables._locks; ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo); bool resizeDesired = false; bool forceRehash = false; - bool lockTaken = false; - try + using (locks[lockNo].EnterScope()) { - 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 != Dictionary._tables) @@ -2521,13 +2502,6 @@ private bool TryAdd(TAlternateKey key, TValue value, bool updateIfExists, out TV forceRehash = 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. // See comments in TryAddInternal. @@ -2621,7 +2595,7 @@ public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TKey actualK while (true) { - object[] locks = tables._locks; + Lock[] locks = tables._locks; ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo); // Do a hot read on number of items stored in the bucket. If it's empty, we can avoid