Propriété Thread-safe List <T>

123

Je veux une implémentation de en List<T>tant que propriété qui peut être utilisée sans aucun doute en toute sécurité avec les threads.

Quelque chose comme ça:

private List<T> _list;

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

Il semble que j'ai encore besoin de renvoyer une copie (clonée) de la collection, donc si quelque part nous itérons la collection et en même temps la collection est définie, aucune exception n'est soulevée.

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

Xaqron
la source
4
utilisez des verrous, cela devrait le faire.
atoMerz
Peut utiliser une implémentation thread-safe de IList<T>(vs List<T>)?
Greg
2
Avez-vous vérifié SynchronizedCollection <T> ?
Saturn Technologies
Utilisez BlockingCollection ou ConcurrentDictionary
kumar chandraketu
Quelles opérations devez-vous effectuer avec l'objet derrière la propriété? Est-il possible que vous n'ayez pas besoin de tout ce qui List<T>met en œuvre? Si oui, pourriez-vous s'il vous plaît fournir une interface dont vous avez besoin au lieu de poser des questions sur tout ce qui List<T>existe déjà?
Victor Yarema

Réponses:

186

Si vous ciblez .Net 4, il existe quelques options dans System.Collections.Concurrent Namespace

Vous pouvez utiliser ConcurrentBag<T>dans ce cas au lieu deList<T>

Bala R
la source
5
Comme List <T> et contrairement à Dictionary, ConcurrentBag accepte les doublons.
The Light
115
ConcurrentBagest une collection non ordonnée, donc contrairement à List<T>elle ne garantit pas la commande. Vous ne pouvez pas non plus accéder aux éléments par index.
Radek Stromský
11
@ RadekStromský a raison, et dans le cas où vous souhaitez une liste simultanée ordonnée, vous pouvez essayer ConcurrentQueue (FIFO) ou ConcurrentStack (LIFO) .
Caio Cunha
7
Peut-être que SynchronizedCollection <T> ?
Saturn Technologies
12
ConcurrentBag n'implémente pas IList et n'est pas réellement une version thread-safe de List
Vasyl Zvarydchuk
87

Même s'il a obtenu le plus de votes, on ne peut généralement pas le System.Collections.Concurrent.ConcurrentBag<T>remplacer System.Collections.Generic.List<T>tel quel (Radek Stromský l'a déjà souligné) non commandé.

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

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

J'espère que cela pourra aider.

Christoph
la source
3
Je pleure que ce n'est pas dans la bibliothèque principale: {Une simple collection synchronisée est souvent tout ce qui est nécessaire.
user2864740
Discussion utile supplémentaire sur cette option: stackoverflow.com/a/4655236/12484
Jon Schneider
2
Il est bien caché car obsolète, en faveur des classes dans System.Collections.Concurrent.
denfromufa
3
Et non disponible en .net core
denfromufa
2
@denfromufa on dirait qu'ils aiment ils ont ajouté ceci dans .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94
17

Je pense que créer un exemple de classe ThreadSafeList serait facile:

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 clonez simplement la liste avant de demander un énumérateur, et donc toute énumération fonctionne sur une copie qui ne peut pas être modifiée en cours d'exécution.

Tejs
la source
1
N'est-ce pas un clone superficiel? S'il Ts'agit d'un type de référence, cela ne retournera-t-il pas simplement une nouvelle liste contenant des références à tous les objets d'origine? Si tel est le cas, cette approche peut encore causer des problèmes de thread car les objets de la liste peuvent être accédés par plusieurs threads via différentes "copies" de la liste.
Joël B
3
Correct, c'est une copie superficielle. Le but était simplement d'avoir un ensemble cloné qui serait sûr pour itérer (donc newListaucun élément n'a été ajouté ou supprimé qui invaliderait l'énumérateur).
Tejs
7
Le _lock doit-il être statique?
Mike Ward
4
Une autre pensée. Cette implémentation est-elle threadsafe pour plusieurs écrivains? Si ce n'est pas le cas, il devrait peut-être s'appeler ReadSafeList.
Mike Ward
5
@MikeWard - Je ne pense pas qu'il devrait être, tout exemple se verrouille quand une instance est en cours cloné!
Josh M.
11

Même la réponse acceptée est ConcurrentBag, je ne pense pas que ce soit un véritable remplacement de la liste dans tous les cas, comme le dit le commentaire de Radek à la réponse: "ConcurrentBag est une collection non ordonnée, donc contrairement à List, il ne garantit pas la commande. De plus, vous ne pouvez pas accéder aux éléments par index ".

Donc, si vous utilisez .NET 4.0 ou version ultérieure, une solution de contournement pourrait être d'utiliser ConcurrentDictionary avec l'entier TKey comme index de tableau et TValue comme valeur de tableau. C'est une 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 aux index et la commande (nous ne pouvons pas nous fier à la commande car c'est une table de hachage sous le capot, mais l'implémentation actuelle de .NET économise l'ordre des éléments ajoutés).

tytyryty
la source
1
veuillez fournir les raisons de -1
tytyryty
Je n'ai pas voté contre et il n'y a aucune raison pour cela IMO. Vous avez raison mais le concept est déjà évoqué dans certaines réponses. Pour moi, le fait était qu'il existe une nouvelle collection thread-safe dans .NET 4.0 dont je n'étais pas au courant. Pas sûr utilisé Sac ou Collection pour la situation. +1
Xaqron
2
Cette réponse pose plusieurs problèmes: 1) ConcurrentDictionaryest un dictionnaire, pas une liste. 2) Il n'est pas garanti de conserver la commande, comme l'indique votre propre réponse, ce qui contredit la raison pour laquelle vous avez affiché une réponse. 3) Il renvoie à une vidéo sans apporter les citations pertinentes dans cette réponse (ce qui pourrait ne pas être en accord avec leur licence de toute façon).
jpmc26
Vous ne pouvez pas vous fier à des choses comme current implementationsi cela n'est pas explicitement garanti par la documentation. La mise en œuvre peut changer à tout moment sans préavis.
Victor Yarema
@ jpmc26, oui ce n'est pas un remplacement complet pour List bien sûr, mais il en va de même avec ConcurrentBag comme réponse acceptée - ce n'est pas un remplacement strict de List mais un contournement. Pour répondre à vos préoccupations: 1) ConcurrentDictionary est un dictionnaire pas une liste que vous avez raison, mais la liste a un tableau derrière, qui peut indexer dans O (1) comme le dictionnaire avec int comme clé 2) oui l'ordre n'est pas garanti par doc ( même s'il est conservé), mais accepté ConcurrentBag ne peut pas non plus garantir l'ordre dans les scénarios multithread
tytyryty
9

La ArrayListclasse de C # a une Synchronizedméthode.

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

Cela renvoie un wrapper thread-safe autour de toute instance de IList. Toutes les opérations doivent être effectuées via le wrapper pour garantir la sécurité du fil.

Hani Nakhli
la source
1
De quelle langue parlez-vous?
John Demetriou
Java? Une des rares fonctionnalités qui me manque à ce sujet. Mais il est généralement écrit comme suit: Collections.synchronizedList (new ArrayList ());
Nick
2
Ceci est valide en C # en supposant que vous ayez un System.Collections using ou que vous puissiez utiliser var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234
5

Si vous regardez le code source de List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), vous remarquerez qu'il y a une classe là (qui est bien sûr interne - pourquoi, Microsoft, pourquoi?!?!) appelé SynchronizedList of T.Je suis en train de copier-coller 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 savaient qu'une meilleure implémentation en utilisant SemaphoreSlim pourrait être créée, mais ils ne l'ont pas fait.

Sidérite Zackwehdex
la source
2
+1 Verrouiller toute la collection ( _root) à chaque accès (lecture / écriture) en fait une solution lente. Il vaut peut-être mieux que cette classe reste interne.
Xaqron
3
Cette implémentation n'est pas thread-safe. Il lance toujours "System.InvalidOperationException: 'La collection a été modifiée; l'opération d'énumération ne peut pas s'exécuter.'"
Raman Zhylich
2
Cela n'est pas lié à la sécurité des threads, mais au fait que vous itérez et modifiez la collection. L'exception est levée par l'énumérateur lorsqu'il voit que la liste a été modifiée. Pour contourner ce problème, vous devez implémenter votre propre IEnumerator ou modifier le code afin qu'il n'itère pas et ne modifie pas la même collection en même temps.
Siderite Zackwehdex
Ce n'est pas thread-safe car la collection peut être modifiée pendant les méthodes "synchronisées". Qu'absolument est une partie de la sécurité de fil. Considérez un thread appelle Clear()après un autre appel this[index]mais avant que le verrou ne soit activé. indexn'est plus sûr à utiliser et lèvera une exception lors de son exécution finale.
Suncat2000
2

Vous pouvez également utiliser le plus primitif

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

quel verrou utilise (voir cet article C # Verrouiller un objet qui est réaffecté dans le bloc de verrouillage ).

Si vous vous attendez à des exceptions dans le code, ce n'est pas sûr mais cela vous permet de faire quelque chose comme ce qui suit:

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 bonnes choses à 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 devrait sortir dans les bons morceaux (mon utilisation de cela consistait à obtenir une sortie à l'écran à partir d'un processus externe)

J'aime vraiment la simplicité + la transparence de ThreadSafeList + qui fait l'essentiel pour arrêter les plantages

JonnyRaa
la source
2

Dans .NET Core (n'importe quelle version), vous pouvez utiliser ImmutableList , qui a toutes les fonctionnalités de List<T>.

JotaBe
la source
1

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

_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 les autres l'ont souligné.

Jonathan Henson
la source
1

Il semble que beaucoup de personnes qui trouvent cela souhaitent une collection de taille dynamique indexée thread-safe. La chose la plus proche et la plus simple que je connaisse serait.

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

Cela vous obligerait à vous assurer que votre clé est correctement incriminée si vous souhaitez un comportement d'indexation normal. Si vous faites attention, .count peut suffire comme clé pour toute nouvelle paire valeur / clé que vous ajoutez.

user2163234
la source
1
Pourquoi la clé devrait-elle être incriminée alors que ce n'était pas la faute de la clé?
Suncat2000
@ Suncat2000 ha!
Richard II le
1

Je suggérerais à toute personne traitant d'un List<T>scénario multi-threading de jeter un coup d'œil aux collections immuables, en particulier l' ImmutableArray .

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

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

Peut également être utile lorsque vous devez implémenter une sorte de comportement de type transaction (c.-à-d. Annuler une opération d'insertion / mise à jour / suppression en cas d'échec)

espace publicitaire
la source
-1

Voici le cours que vous avez demandé:

namespace AI.Collections {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;

    /// <summary>
    ///     Just a simple thread safe collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <value>Version 1.5</value>
    /// <remarks>TODO replace locks with AsyncLocks</remarks>
    [DataContract( IsReference = true )]
    public class ThreadSafeList<T> : IList<T> {
        /// <summary>
        ///     TODO replace the locks with a ReaderWriterLockSlim
        /// </summary>
        [DataMember]
        private readonly List<T> _items = new List<T>();

        public ThreadSafeList( IEnumerable<T> items = null ) { this.Add( items ); }

        public long LongCount {
            get {
                lock ( this._items ) {
                    return this._items.LongCount();
                }
            }
        }

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

        IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }

        public void Add( T item ) {
            if ( Equals( default( T ), item ) ) {
                return;
            }
            lock ( this._items ) {
                this._items.Add( item );
            }
        }

        public Boolean TryAdd( T item ) {
            try {
                if ( Equals( default( T ), item ) ) {
                    return false;
                }
                lock ( this._items ) {
                    this._items.Add( item );
                    return true;
                }
            }
            catch ( NullReferenceException ) { }
            catch ( ObjectDisposedException ) { }
            catch ( ArgumentNullException ) { }
            catch ( ArgumentOutOfRangeException ) { }
            catch ( ArgumentException ) { }
            return false;
        }

        public void Clear() {
            lock ( this._items ) {
                this._items.Clear();
            }
        }

        public bool Contains( T item ) {
            lock ( this._items ) {
                return this._items.Contains( item );
            }
        }

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

        public bool Remove( T item ) {
            lock ( this._items ) {
                return this._items.Remove( item );
            }
        }

        public int Count {
            get {
                lock ( this._items ) {
                    return this._items.Count;
                }
            }
        }

        public bool IsReadOnly { get { return false; } }

        public int IndexOf( T item ) {
            lock ( this._items ) {
                return this._items.IndexOf( item );
            }
        }

        public void Insert( int index, T item ) {
            lock ( this._items ) {
                this._items.Insert( index, item );
            }
        }

        public void RemoveAt( int index ) {
            lock ( this._items ) {
                this._items.RemoveAt( index );
            }
        }

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

        /// <summary>
        ///     Add in an enumerable of items.
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="asParallel"></param>
        public void Add( IEnumerable<T> collection, Boolean asParallel = true ) {
            if ( collection == null ) {
                return;
            }
            lock ( this._items ) {
                this._items.AddRange( asParallel
                                              ? collection.AsParallel().Where( arg => !Equals( default( T ), arg ) )
                                              : collection.Where( arg => !Equals( default( T ), arg ) ) );
            }
        }

        public Task AddAsync( T item ) {
            return Task.Factory.StartNew( () => { this.TryAdd( item ); } );
        }

        /// <summary>
        ///     Add in an enumerable of items.
        /// </summary>
        /// <param name="collection"></param>
        public Task AddAsync( IEnumerable<T> collection ) {
            if ( collection == null ) {
                throw new ArgumentNullException( "collection" );
            }

            var produce = new TransformBlock<T, T>( item => item, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } );

            var consume = new ActionBlock<T>( action: async obj => await this.AddAsync( obj ), dataflowBlockOptions: new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } );
            produce.LinkTo( consume );

            return Task.Factory.StartNew( async () => {
                collection.AsParallel().ForAll( item => produce.SendAsync( item ) );
                produce.Complete();
                await consume.Completion;
            } );
        }

        /// <summary>
        ///     Returns a new copy of all items in the <see cref="List{T}" />.
        /// </summary>
        /// <returns></returns>
        public List<T> Clone( Boolean asParallel = true ) {
            lock ( this._items ) {
                return asParallel
                               ? new List<T>( this._items.AsParallel() )
                               : new List<T>( this._items );
            }
        }

        /// <summary>
        ///     Perform the <paramref name="action" /> on each item in the list.
        /// </summary>
        /// <param name="action">
        ///     <paramref name="action" /> to perform on each item.
        /// </param>
        /// <param name="performActionOnClones">
        ///     If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items.
        /// </param>
        /// <param name="asParallel">
        ///     Use the <see cref="ParallelQuery{TSource}" /> method.
        /// </param>
        /// <param name="inParallel">
        ///     Use the
        ///     <see
        ///         cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" />
        ///     method.
        /// </param>
        public void ForEach( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) {
            if ( action == null ) {
                throw new ArgumentNullException( "action" );
            }
            var wrapper = new Action<T>( obj => {
                try {
                    action( obj );
                }
                catch ( ArgumentNullException ) {
                    //if a null gets into the list then swallow an ArgumentNullException so we can continue adding
                }
            } );
            if ( performActionOnClones ) {
                var clones = this.Clone( asParallel: asParallel );
                if ( asParallel ) {
                    clones.AsParallel().ForAll( wrapper );
                }
                else if ( inParallel ) {
                    Parallel.ForEach( clones, wrapper );
                }
                else {
                    clones.ForEach( wrapper );
                }
            }
            else {
                lock ( this._items ) {
                    if ( asParallel ) {
                        this._items.AsParallel().ForAll( wrapper );
                    }
                    else if ( inParallel ) {
                        Parallel.ForEach( this._items, wrapper );
                    }
                    else {
                        this._items.ForEach( wrapper );
                    }
                }
            }
        }

        /// <summary>
        ///     Perform the <paramref name="action" /> on each item in the list.
        /// </summary>
        /// <param name="action">
        ///     <paramref name="action" /> to perform on each item.
        /// </param>
        /// <param name="performActionOnClones">
        ///     If true, the <paramref name="action" /> will be performed on a <see cref="Clone" /> of the items.
        /// </param>
        /// <param name="asParallel">
        ///     Use the <see cref="ParallelQuery{TSource}" /> method.
        /// </param>
        /// <param name="inParallel">
        ///     Use the
        ///     <see
        ///         cref="Parallel.ForEach{TSource}(System.Collections.Generic.IEnumerable{TSource},System.Action{TSource})" />
        ///     method.
        /// </param>
        public void ForAll( Action<T> action, Boolean performActionOnClones = true, Boolean asParallel = true, Boolean inParallel = false ) {
            if ( action == null ) {
                throw new ArgumentNullException( "action" );
            }
            var wrapper = new Action<T>( obj => {
                try {
                    action( obj );
                }
                catch ( ArgumentNullException ) {
                    //if a null gets into the list then swallow an ArgumentNullException so we can continue adding
                }
            } );
            if ( performActionOnClones ) {
                var clones = this.Clone( asParallel: asParallel );
                if ( asParallel ) {
                    clones.AsParallel().ForAll( wrapper );
                }
                else if ( inParallel ) {
                    Parallel.ForEach( clones, wrapper );
                }
                else {
                    clones.ForEach( wrapper );
                }
            }
            else {
                lock ( this._items ) {
                    if ( asParallel ) {
                        this._items.AsParallel().ForAll( wrapper );
                    }
                    else if ( inParallel ) {
                        Parallel.ForEach( this._items, wrapper );
                    }
                    else {
                        this._items.ForEach( wrapper );
                    }
                }
            }
        }
    }
}
Protiguë
la source
La version sur Google Drive est mise à jour lorsque je mets à jour la classe. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous
Pourquoi this.GetEnumerator();quand @Tejs le suggère this.Clone().GetEnumerator();?
Cœur
Pourquoi [DataContract( IsReference = true )]?
Cœur
La dernière version est maintenant sur GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous
J'ai trouvé et corrigé deux petits bugs dans les méthodes Add (). FYI.
Protiguous
-3

Fondamentalement, si vous souhaitez énumérer en toute sécurité, vous devez utiliser lock.

Veuillez vous référer à MSDN à ce sujet. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Voici une partie de MSDN qui pourrait vous intéresser:

Les membres statiques publics (partagés dans Visual Basic) de ce type sont thread-safe. Les membres d'instance ne sont pas garantis d'être thread-safe.

Une liste peut prendre en charge plusieurs lecteurs simultanément, tant que la collection n'est pas modifiée. L'énumération via une collection n'est pas intrinsèquement une procédure thread-safe. Dans les rares cas où une énumération est en conflit avec un ou plusieurs accès en écriture, le seul moyen de garantir la sécurité des threads est de verrouiller la collection pendant toute l'énumération. Pour permettre l'accès à la collection par plusieurs threads pour la lecture et l'écriture, vous devez implémenter votre propre synchronisation.

istudy0
la source
2
Pas vrai du tout. Vous pouvez utiliser des ensembles simultanés.
ANeves
-3

Voici la classe pour la liste thread-safe sans verrou

 public class ConcurrentList   
    {
        private long _i = 1;
        private ConcurrentDictionary<long, T> dict = new ConcurrentDictionary<long, T>();  
        public int Count()
        {
            return dict.Count;
        }
         public List<T> ToList()
         {
            return dict.Values.ToList();
         }

        public T this[int i]
        {
            get
            {
                long ii = dict.Keys.ToArray()[i];
                return dict[ii];
            }
        }
        public void Remove(T item)
        {
            T ov;
            var dicItem = dict.Where(c => c.Value.Equals(item)).FirstOrDefault();
            if (dicItem.Key > 0)
            {
                dict.TryRemove(dicItem.Key, out ov);
            }
            this.CheckReset();
        }
        public void RemoveAt(int i)
        {
            long v = dict.Keys.ToArray()[i];
            T ov;
            dict.TryRemove(v, out ov);
            this.CheckReset();
        }
        public void Add(T item)
        {
            dict.TryAdd(_i, item);
            _i++;
        }
        public IEnumerable<T> Where(Func<T, bool> p)
        {
            return dict.Values.Where(p);
        }
        public T FirstOrDefault(Func<T, bool> p)
        {
            return dict.Values.Where(p).FirstOrDefault();
        }
        public bool Any(Func<T, bool> p)
        {
            return dict.Values.Where(p).Count() > 0 ? true : false;
        }
        public void Clear()
        {
            dict.Clear();
        }
        private void CheckReset()
        {
            if (dict.Count == 0)
            {
                this.Reset();
            }
        }
        private void Reset()
        {
            _i = 1;
        }
    }
Suraj Mangal
la source
Ce n'est pas threadsafe
Aldracor
_i ++ n'est pas threadsafe. vous devez utiliser un ajout atomique lorsque vous l'incrémentez et le marquez probablement aussi comme volatil. CheckReset () n'est pas threadsafe. Tout peut arriver entre la vérification conditionnelle et l'appel à Reset (). N'écrivez pas vos propres utilitaires multithreading.
Chris Rollins
-15

Utilisez l' lockinstruction pour ce faire. ( Lisez ici pour plus d'informations. )

private List<T> _list;

private List<T> MyT
{
    get { return _list; }
    set
    {
        //Lock so only one thread can change the value at any given time.
        lock (_list)
        {
            _list = value;
        }
    }
}

Pour votre information, ce n'est probablement pas exactement ce que vous demandez - vous voulez probablement verrouiller plus loin dans votre code, mais je ne peux pas le supposer. Jetez un œil àlock mot clé et adaptez son utilisation à votre situation spécifique.

Si vous en avez besoin, vous pouvez lockà la fois bloquer getet setutiliser la _listvariable qui ferait en sorte qu'une lecture / écriture ne puisse pas se produire en même temps.

Josh M.
la source
1
Cela ne résoudra pas son problème; il arrête uniquement les threads de définir la référence, sans ajouter à la liste.
Tejs
Et si un thread définit la valeur tandis qu'un autre itère la collection (c'est possible avec votre code).
Xaqron
Comme je l'ai dit, le verrou devra probablement être déplacé plus loin dans le code. Ceci est juste un exemple d'utilisation de l'instruction de verrouillage.
Josh M.
2
@Joel Mueller: Bien sûr, si vous fabriquez un exemple idiot comme celui-là. J'essaie simplement d'illustrer que le demandeur devrait examiner la lockdéclaration. En utilisant un exemple similaire, je pourrais affirmer que nous ne devrions pas utiliser de boucles for, car vous pourriez bloquer l'application avec pratiquement aucun effort:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.
5
Je n'ai jamais prétendu que votre code signifiait une impasse instantanée. C'est une mauvaise réponse à cette question particulière pour les raisons suivantes: 1) Il ne protège pas contre le contenu de la liste étant modifié lors de l'énumération de la liste, ou par deux threads à la fois. 2) Verrouiller le setter mais pas le getter signifie que la propriété n'est pas vraiment thread-safe. 3) Le verrouillage de toute référence accessible de l'extérieur de la classe est largement considéré comme une mauvaise pratique, car cela augmente considérablement les chances de blocage accidentel. C'est pourquoi lock (this)et lock (typeof(this))sont de grands non-non.
Joel Mueller