Because the ConcurrentDictionary
namespace DotnetCommon.DataStructures
{
using System.Collections.Generic;
using System.Threading;
/// <summary>
/// Generic Atomic (thread-safe) Dictionary.
///
/// Because the ConcurrentDictionary<TKey, TValue> class includes methods which
/// are not fully atomic. The overloads of GetOrAdd and AddOrUpdate methods which
/// accept delegates as parameters will invoke these delegates outside the
/// synchronization locks.
/// </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>
/// <list type="bullet">
/// <item><description>Ref: https://www.dotnetcurry.com/csharp/1466/csharp-dotnet-collection-class</description></item>
/// <item><description>Also see: https://github.com/microsoft/FASTER/blob/master/cs/src/core/Utilities/SafeConcurrentDictionary.cs</description></item>
/// <item><description>Snippet: https://jonlabelle.com/snippets/view/csharp/genericatomic-thread-safe-dictionary-class</description></item>
/// </list>
/// </remarks>
public class AtomicDictionary<TKey, TValue>
{
private readonly ReaderWriterLockSlim _dictionaryLock = new ReaderWriterLockSlim();
private readonly Dictionary<TKey, TValue> _dictionary;
public AtomicDictionary()
{
_dictionary = new Dictionary<TKey, TValue>();
}
public TValue this[TKey key]
{
get
{
_dictionaryLock.EnterReadLock();
try
{
return _dictionary[key];
}
finally
{
_dictionaryLock.ExitReadLock();
}
}
set
{
_dictionaryLock.EnterWriteLock();
try
{
_dictionary[key] = value;
}
finally
{
_dictionaryLock.ExitWriteLock();
}
}
}
public void Add(TKey key, TValue value)
{
_dictionaryLock.EnterWriteLock();
try
{
_dictionary.Add(key, value);
}
finally
{
_dictionaryLock.ExitWriteLock();
}
}
// ------------------------------------------------------------------------
// Additional prop/methods below taken from:
// https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim#examples
// ------------------------------------------------------------------------
public int Count
{
get
{
_dictionaryLock.EnterReadLock();
try
{
return _dictionary.Count;
}
finally
{
_dictionaryLock.ExitReadLock();
}
}
}
public AddOrUpdateStatus AddOrUpdate(
TKey key,
TValue value,
IEqualityComparer<TValue> valueEqualityComparer = null
)
{
valueEqualityComparer ??= EqualityComparer<TValue>.Default;
_dictionaryLock.EnterUpgradeableReadLock();
try
{
TValue result;
if (_dictionary.TryGetValue(key, out result))
{
if (valueEqualityComparer.Equals(result, value))
{
return AddOrUpdateStatus.Unchanged;
}
else
{
_dictionaryLock.EnterWriteLock();
try
{
_dictionary[key] = value;
}
finally
{
_dictionaryLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
_dictionaryLock.EnterWriteLock();
try
{
_dictionary.Add(key, value);
}
finally
{
_dictionaryLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
_dictionaryLock.ExitUpgradeableReadLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
}
public bool AddWithTimeout(TKey key, TValue value, int timeout)
{
if (_dictionaryLock.TryEnterWriteLock(timeout))
{
try
{
_dictionary.Add(key, value);
}
finally
{
_dictionaryLock.ExitWriteLock();
}
return true;
}
return false;
}
public void Remove(TKey key)
{
_dictionaryLock.EnterWriteLock();
try
{
_dictionary.Remove(key);
}
finally
{
_dictionaryLock.ExitWriteLock();
}
}
~AtomicDictionary()
{
_dictionaryLock?.Dispose();
}
}
}
// ---------------------------------------------------------------------------------------------------------
// Another thread-safe generic dictionary class
// Source: https://github.com/madhatter22/LinqToLdap/blob/master/LinqToLdap/Collections/SafeDictionary.cs
// ---------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace LinqToLdap.Collections
{
internal class SafeDictionary<TKey, TValue>
{
private ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private Dictionary<TKey, TValue> _internal = new Dictionary<TKey, TValue>();
~SafeDictionary()
{
var locker = _locker;
if (locker != null)
{
locker.Dispose();
_locker = locker = null;
}
var dictionary = _internal;
if (dictionary != null)
{
dictionary.Clear();
_internal = dictionary = null;
}
}
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
_locker.EnterReadLock();
try
{
if (_internal.ContainsKey(key))
{
return _internal[key];
}
}
finally
{
_locker.ExitReadLock();
}
var value = valueFactory(key);
_locker.EnterWriteLock();
try
{
if (!_internal.ContainsKey(key))
{
_internal.Add(key, value);
}
return _internal[key];
}
finally
{
_locker.ExitWriteLock();
}
}
public ReadOnlyDictionary<TKey, TValue> ToReadOnly()
{
_locker.EnterReadLock();
try
{
return new ReadOnlyDictionary<TKey, TValue>(_internal.ToDictionary(d => d.Key, d => d.Value));
}
finally
{
_locker.ExitReadLock();
}
}
public bool TryGetValue(TKey key, out TValue value)
{
_locker.EnterReadLock();
try
{
if (_internal.ContainsKey(key))
{
value = _internal[key];
return true;
}
value = default;
return false;
}
finally
{
_locker.ExitReadLock();
}
}
public void TryAdd(TKey key, TValue value)
{
_locker.EnterWriteLock();
try
{
if (!_internal.ContainsKey(key))
{
_internal.Add(key, value);
}
}
finally
{
_locker.ExitWriteLock();
}
}
public void AddOrUpdate(TKey key, TValue value)
{
_locker.EnterWriteLock();
try
{
_internal[key] = value;
}
finally
{
_locker.ExitWriteLock();
}
}
}
}