I've recently been using HashSet and locking on each method, I found this to not only be a lot of work (was using it in a lot of places) but I started to see inconsistency in my code.
I later decided to make a Concurrent version of HashSet, the reason I don't use a version of ConcurrentDictionary
is because I only need a value, but can't duplicate the values so I also can't use a ConcurrentBag
, I found HashSet to be my best option, but it lacked a Concurrent thread safe version, this is where my class came in...
I have a few questions about this. Mainly, is this implementation thread-safe and do I have anything to worry about? Is there anything else I can do to make it better, and am I right in using a ConcurrentHashSet, or does anyone have any better ideas for a collection in mind?
One final thing was that I coded the IEnumerator myself whilst half asleep as the original lacked the functionality for this, and I needed to use it inside of foreach loops. I'm pretty sure the rest is thread safe but I guess I'm saying I'm not confident enough with the code I wrote to be thread-safe, its just it seems to step out of the class a bit with a Interface and I was worried that may affect it in some kind of way.
IEnumerator:
public IEnumerator<T> GetEnumerator()
{
_lock.EnterWriteLock();
try
{
return _hashSet.GetEnumerator();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
Full Implementation:
public class ConcurrentHashSet<T> : IDisposable, IEnumerable<T>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly HashSet<T> _hashSet = new HashSet<T>();
public IEnumerator<T> GetEnumerator()
{
_lock.EnterWriteLock();
try
{
return _hashSet.GetEnumerator();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool TryAdd(T item)
{
_lock.EnterWriteLock();
try
{
return _hashSet.Add(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
public void Clear()
{
_lock.EnterWriteLock();
try
{
_hashSet.Clear();
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
public bool Contains(T item)
{
_lock.EnterReadLock();
try
{
return _hashSet.Contains(item);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
public bool TryRemove(T item)
{
_lock.EnterWriteLock();
try
{
return _hashSet.Remove(item);
}
finally
{
if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
}
}
public int Count
{
get
{
_lock.EnterReadLock();
try
{
return _hashSet.Count;
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
}
public T FirstOrDefault(Func<T, bool> predicate)
{
_lock.EnterReadLock();
try
{
return _hashSet.FirstOrDefault(predicate);
}
finally
{
if (_lock.IsReadLockHeld) _lock.ExitReadLock();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_lock?.Dispose();
}
}
}