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