Votre mise en œuvre est correcte. Malheureusement, le cadre de travail .NET ne fournit pas de type de hashset concurrent intégré. Il existe toutefois des solutions de contournement.
ConcurrentDictionary (recommandé)
La première consiste à utiliser la classe ConcurrentDictionary<TKey, TValue>
dans l'espace de noms System.Collections.Concurrent
. Dans ce cas, la valeur est inutile, nous pouvons donc utiliser un simple byte
(1 octet en mémoire).
private ConcurrentDictionary<string, byte> _data;
C'est l'option recommandée car le type est sûr pour les threads et offre les mêmes avantages que le type HashSet<T>
sauf que la clé et la valeur sont des objets différents.
Source : Social MSDN
Auto-application
Enfin, comme vous l'avez fait, vous pouvez mettre en œuvre votre propre type de données, en utilisant des verrous ou d'autres moyens que .NET met à votre disposition pour être à l'abri des threads. Voici un excellent exemple : Comment implémenter ConcurrentHashSet dans .Net
Le seul inconvénient de cette solution est que le type HashSet<T>
ne permet pas officiellement l'accès simultané, même pour les opérations de lecture.
Je cite le code de l'article lié (écrit à l'origine par 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 : Déplacer les méthodes de verrouillage de l'entrée à l'extérieur de la try
car ils pourraient lever une exception et exécuter les instructions contenues dans le bloc finally
blocs.
ConcurrentBag (déconseillé)
L'utilisation de ConcurrentBag<T>
n'est pas conseillé, car ce type permet uniquement d'insérer un élément donné et de retirer un élément aléatoire de manière sûre pour les threads. Cette classe est conçue pour faciliter les scénarios producteur-consommateur, ce qui n'est pas l'objectif de l'OP (plus d'explications aquí ).
Les autres opérations (par exemple, celles fournies par les méthodes d'extension) font pas supporter une utilisation simultanée. La documentation MSDN met en garde : " Tous les membres publics et protégés de ConcurrentBag sont à l'abri des threads et peuvent être utilisés simultanément à partir de plusieurs threads. Cependant, les membres accessibles via l'une des interfaces mises en œuvre par ConcurrentBag, y compris les méthodes d'extension, ne sont pas garantis comme étant à l'abri des threads et peuvent nécessiter une synchronisation de la part de l'appelant. "