web-dev-qa-db-fra.com

Propriété <T> de la liste de sécurité des threads

Je veux une implémentation de List<T> comme une propriété qui peut être utilisée sans aucun doute comme un thread.

Quelque chose comme ça:

private List<T> _list;

private List<T> MyT
{
    get { // return a copy of _list; }
    set { _list = value; }
}

Il semble que je doive toujours renvoyer une copie (clonée) de la collection. Par conséquent, si nous effectuons une itération de la collection et que la collection est définie en même temps, aucune exception n'est générée.

Comment implémenter une propriété de collection thread-safe?

104
Xaqron

Si vous ciblez .Net 4, il existe quelques options dans System.Collections.Concurrent Espace de noms

Vous pouvez utiliser ConcurrentBag<T> Dans ce cas au lieu de List<T>

168
Bala R

Même s'il a obtenu le plus de votes, on ne peut généralement pas prendre System.Collections.Concurrent.ConcurrentBag<T> comme remplacement thread-safe de System.Collections.Generic.List<T> _ tel qu’il est (Radek Stromský l’a déjà fait remarquer) non commandé.

Mais il y a une classe appelée System.Collections.Generic.SynchronizedCollection<T> cela fait déjà partie du framework .NET 3.0, mais il est aussi bien caché dans un endroit où on ne s’attend pas à ce qu’il soit peu connu et que vous n’ayez probablement jamais trébuché dessus (du moins je ne l’ai jamais fait) .

SynchronizedCollection<T> est compilé dans Assembly System.ServiceModel.dll (qui fait partie du profil du client mais pas de la bibliothèque de classes portable).

J'espère que ça t'as aidé.

80
Christoph

Je pense qu'il serait facile de créer un exemple de classe ThreadSafeList:

public class ThreadSafeList<T> : IList<T>
{
    protected List<T> _interalList = new List<T>();

    // Other Elements of IList implementation

    public IEnumerator<T> GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    protected static object _lock = new object();

    public List<T> Clone()
    {
        List<T> newList = new List<T>();

        lock (_lock)
        {
            _interalList.ForEach(x => newList.Add(x));
        }

        return newList;
    }
}

Vous devez simplement cloner la liste avant de demander un énumérateur. Ainsi, toute énumération fonctionne avec une copie qui ne peut pas être modifiée en cours d'exécution.

16
Tejs

Même la réponse acceptée est ConcurrentBag, je ne pense pas que ce soit un remplacement réel de la liste dans tous les cas, comme le dit le commentaire de Radek à la réponse: "ConcurrentBag est une collection non ordonnée. Par conséquent, contrairement à List, elle ne garantit pas la commande. ".

Ainsi, si vous utilisez .NET 4.0 ou une version ultérieure, une solution de contournement pourrait consister à utiliser ConcurrentDictionary avec TKey entier comme index de tableau et TValue comme valeur de tableau. C'est la méthode recommandée pour remplacer la liste dans le cours C # Concurrent Collections de Pluralsight . ConcurrentDictionary résout les deux problèmes mentionnés ci-dessus: l'accès à l'index et la commande (nous ne pouvons pas compter sur la commande car c'est une table de hachage sous le capot, mais l'implémentation .NET actuelle enregistre l'ordre d'ajout d'éléments).

5
tytyryty

Vous pouvez utiliser:

var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());

créer une liste de tableaux sécurisée de threads

4
Hani Nakhli

Si vous examinez le code source de List of T ( https://referencesource.Microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), vous remarquerez qu'il existe un class there (qui est bien entendu interne - pourquoi, Microsoft, pourquoi?!?!) appelée SynchronizedList of T. Je copie le code ici:

   [Serializable()]
    internal class SynchronizedList : IList<T> {
        private List<T> _list;
        private Object _root;

        internal SynchronizedList(List<T> list) {
            _list = list;
            _root = ((System.Collections.ICollection)list).SyncRoot;
        }

        public int Count {
            get {
                lock (_root) { 
                    return _list.Count; 
                }
            }
        }

        public bool IsReadOnly {
            get {
                return ((ICollection<T>)_list).IsReadOnly;
            }
        }

        public void Add(T item) {
            lock (_root) { 
                _list.Add(item); 
            }
        }

        public void Clear() {
            lock (_root) { 
                _list.Clear(); 
            }
        }

        public bool Contains(T item) {
            lock (_root) { 
                return _list.Contains(item);
            }
        }

        public void CopyTo(T[] array, int arrayIndex) {
            lock (_root) { 
                _list.CopyTo(array, arrayIndex);
            }
        }

        public bool Remove(T item) {
            lock (_root) { 
                return _list.Remove(item);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            lock (_root) { 
                return _list.GetEnumerator();
            }
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator() {
            lock (_root) { 
                return ((IEnumerable<T>)_list).GetEnumerator();
            }
        }

        public T this[int index] {
            get {
                lock(_root) {
                    return _list[index];
                }
            }
            set {
                lock(_root) {
                    _list[index] = value;
                }
            }
        }

        public int IndexOf(T item) {
            lock (_root) {
                return _list.IndexOf(item);
            }
        }

        public void Insert(int index, T item) {
            lock (_root) {
                _list.Insert(index, item);
            }
        }

        public void RemoveAt(int index) {
            lock (_root) {
                _list.RemoveAt(index);
            }
        }
    }

Personnellement, je pense qu'ils connaissaient une meilleure implémentation utilisant SemaphoreSlim pourrait être créé, mais n'y sont pas parvenus.

3
Siderite Zackwehdex

Vous pouvez également utiliser le plus primitif

Monitor.Enter(lock);
Monitor.Exit(lock);

quel verrou utilise (voir cet article C # Verrouillage d'un objet réaffecté dans le bloc de verrouillage ).

Si vous attendez des exceptions dans le code, cela n’est pas sûr, mais cela vous permet de procéder de la manière suivante:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

public class Something
{
    private readonly object _lock;
    private readonly List<string> _contents;

    public Something()
    {
        _lock = new object();

        _contents = new List<string>();
    }

    public Modifier StartModifying()
    {
        return new Modifier(this);
    }

    public class Modifier : IDisposable
    {
        private readonly Something _thing;

        public Modifier(Something thing)
        {
            _thing = thing;

            Monitor.Enter(Lock);
        }

        public void OneOfLotsOfDifferentOperations(string input)
        {
            DoSomethingWith(input);
        }

        private void DoSomethingWith(string input)
        {
            Contents.Add(input);
        }

        private List<string> Contents
        {
            get { return _thing._contents; }
        }

        private object Lock
        {
            get { return _thing._lock; }
        }

        public void Dispose()
        {
            Monitor.Exit(Lock);
        }
    }
}

public class Caller
{
    public void Use(Something thing)
    {
        using (var modifier = thing.StartModifying())
        {
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("B");

            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
        }
    }
}

Une des choses intéressantes à ce sujet est que vous obtiendrez le verrou pour la durée de la série d'opérations (plutôt que de verrouiller chaque opération). Ce qui signifie que la sortie doit apparaître dans les bons morceaux (mon utilisation consistait à obtenir une sortie à l'écran à partir d'un processus externe)

J'aime beaucoup la simplicité et la transparence de ThreadSafeList +, qui joue un rôle important dans l'arrêt des plantages.

2
JonnyRaa

Je crois que _list.ToList() vous en fera une copie. Vous pouvez également l'interroger si vous avez besoin de:

_list.Select("query here").ToList(); 

Quoi qu'il en soit, MSDN dit que c'est en effet une copie et pas simplement une référence. Oh, et oui, vous devrez verrouiller la méthode set, comme l'ont souligné les autres.

1
Jonathan Henson

Il semble que beaucoup de personnes qui découvrent cela souhaitent une collection indexée dynamiquement et sécurisée pour les threads. La chose la plus proche et la plus facile que je connaisse serait.

System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>

Pour ce faire, vous devez vous assurer que votre clé est correctement incriminée si vous souhaitez un comportement d'indexation normal. Si vous êtes prudent, .count peut suffire en tant que clé pour toute nouvelle paire de valeurs clé que vous ajoutez.

1
user2163234

Je suggérerais à quiconque ayant affaire à un List<T> dans des scénarios multi-threads à regarder Collections immuables en particulier le ImmutableArray .

Je l'ai trouvé très utile quand vous avez:

  1. Relativement peu d'éléments dans la liste
  2. Pas tellement d'opérations de lecture/écriture
  3. Beaucoup d'accès simultanés (c'est-à-dire de nombreux threads qui accèdent à la liste en mode lecture)

Cela peut également être utile lorsque vous devez implémenter un type de comportement semblable à une transaction (c'est-à-dire annuler une opération d'insertion/mise à jour/suppression en cas d'échec).

0
adospace