Sua implementação está correta. Infelizmente, o .NET Framework não fornece um tipo de hashset simultâneo interno. No entanto, existem algumas soluções alternativas.
ConcurrentDictionary (recomendado)
Este primeiro é usar a classe ConcurrentDictionary<TKey, TValue>
no espaço para nome System.Collections.Concurrent
. No caso, o valor é inútil, portanto, podemos usar um simples byte
(1 byte na memória).
private ConcurrentDictionary<string, byte> _data;
Essa é a opção recomendada porque o tipo é seguro para threads e oferece as mesmas vantagens que um HashSet<T>
chave e um valor exceto objetos diferentes.
Fonte: Social MSDN
ConcurrentBag
Se você não se importa com as entradas duplicadas, pode usar a classe ConcurrentBag<T>
no mesmo espaço para nome da classe anterior.
private ConcurrentBag<string> _data;
Auto-implementação
Por fim, como você fez, você pode implementar seu próprio tipo de dados, usando o lock ou outras formas que o .NET fornece para você ser seguro para threads. Aqui está um ótimo exemplo: Como implementar o ConcurrentHashSet no .Net
A única desvantagem dessa solução é que o tipo HashSet<T>
não tem acesso simultâneo oficial, mesmo para operações de leitura.
Cito o código do post vinculado (originalmente escrito por Ben Mosher ).
using System;
using System.Collections.Generic;
using System.Threading;
namespace BlahBlah.Utilities
{
public class ConcurrentHashSet<T> : IDisposable
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly HashSet<T> _hashSet = new HashSet<T>();
#region Implementation of ICollection<T> ...ish
public bool Add(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 Remove(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();
}
}
}
#endregion
#region Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (_lock != null)
_lock.Dispose();
}
~ConcurrentHashSet()
{
Dispose(false);
}
#endregion
}
}
EDIT: Mova os métodos de bloqueio de entrada para fora dos try
blocos, pois eles podem lançar uma exceção e executar as instruções contidas nos finally
blocos.
System.Collections.Concurrent