Thread-sichere List <T> -Eigenschaft


121

Ich möchte eine Implementierung List<T>als Eigenschaft, die ohne Zweifel threadsicher verwendet werden kann.

Etwas wie das:

private List<T> _list;

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

Es scheint immer noch, dass ich eine Kopie (geklont) der Sammlung zurückgeben muss. Wenn wir also irgendwo die Sammlung iterieren und gleichzeitig die Sammlung festgelegt wird, wird keine Ausnahme ausgelöst.

Wie implementiere ich eine thread-sichere Sammlungseigenschaft?


4
Verwenden Sie Schlösser, das sollte es tun.
AtoMerz

Kann eine thread-sichere Implementierung von IList<T>(vs List<T>) verwenden?
Greg


Verwenden Sie BlockingCollection oder ConcurrentDictionary
Kumar Chandraketu

Welche Operationen müssen Sie mit dem Objekt hinter der Immobilie ausführen? Ist es möglich, dass Sie nicht alles benötigen, was List<T>implementiert wird? Wenn ja, können Sie bitte eine Schnittstelle bereitstellen, die Sie benötigen, anstatt nach allem zu fragen, was List<T>bereits vorhanden ist?
Victor Yarema

Antworten:


185

Wenn Sie auf .NET 4 abzielen, gibt es in System.Collections.Concurrent Namespace einige Optionen

Sie könnten ConcurrentBag<T>in diesem Fall anstelle von verwendenList<T>


5
Wie List <T> und anders als Dictionary akzeptiert ConcurrentBag Duplikate.
Das Licht

114
ConcurrentBagist eine ungeordnete Abholung, so dass im Gegensatz List<T>dazu keine Garantie für die Bestellung besteht. Sie können auch nicht über den Index auf Elemente zugreifen.
Radek Stromský

11
@ RadekStromský ist richtig, und wenn Sie eine geordnete gleichzeitige Liste wünschen, können Sie ConcurrentQueue (FIFO) oder ConcurrentStack (LIFO) ausprobieren .
Caio Cunha


12
ConcurrentBag implementiert IList nicht und ist keine thread-sichere Version von List
Vasyl Zvarydchuk

86

Selbst wenn es die meisten Stimmen bekommen hat, kann man es normalerweise nicht System.Collections.Concurrent.ConcurrentBag<T>als System.Collections.Generic.List<T>fadensicheren Ersatz nehmen, da es (Radek Stromský hat bereits darauf hingewiesen) nicht bestellt wurde.

Aber es gibt eine Klasse namens System.Collections.Generic.SynchronizedCollection<T>, die bereits seit .NET 3.0 Teil des Frameworks ist, aber sie ist so gut versteckt an einem Ort, an dem man nicht erwartet, dass sie wenig bekannt ist und Sie wahrscheinlich noch nie darüber gestolpert sind (zumindest nicht) Ich habe nie getan).

SynchronizedCollection<T>wird in die Assembly System.ServiceModel.dll kompiliert (die Teil des Clientprofils, aber nicht der portablen Klassenbibliothek ist).

Hoffentlich hilft das.


3
Ich weine, dass dies nicht in der Kernbibliothek ist: {Eine einfache synchronisierte Sammlung ist oft alles, was benötigt wird.
user2864740

Zusätzliche hilfreiche Diskussion dieser Option: stackoverflow.com/a/4655236/12484
Jon Schneider

2
Es ist gut versteckt, weil es veraltet ist, zugunsten von Klassen in System.Collections.Concurrent.
Denfromufa

3
Und nicht verfügbar in .net Core
denfromufa

2
@denfromufa es sieht so aus, als hätten sie dies in .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/… hinzugefügt
Cirelli94

17

Ich würde denken, dass es einfach wäre, eine Beispiel-ThreadSafeList-Klasse zu erstellen:

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;
    }
}

Sie klonen einfach die Liste, bevor Sie einen Enumerator anfordern. Daher funktioniert jede Aufzählung mit einer Kopie, die während der Ausführung nicht geändert werden kann.


1
Ist das nicht ein flacher Klon? Wenn Tes sich um einen Referenztyp handelt, wird dann nicht einfach eine neue Liste mit Verweisen auf alle Originalobjekte zurückgegeben? In diesem Fall kann dieser Ansatz immer noch zu Threading-Problemen führen, da mehrere Threads über verschiedene "Kopien" der Liste auf die Listenobjekte zugreifen können.
Joel B

3
Richtig, es ist eine flache Kopie. Es ging darum, einfach einen geklonten Satz zu haben, über den sicher iteriert werden kann (daher newListwurden keine Elemente hinzugefügt oder entfernt, die den Enumerator ungültig machen würden).
Tejs

7
Sollte das _lock statisch sein?
Mike Ward

4
Ein anderer Gedanke. Ist diese Implementierung für mehrere Autoren threadsicher? Wenn nicht, sollte es möglicherweise als ReadSafeList bezeichnet werden.
Mike Ward

5
@ MikeWard - Ich denke nicht, dass es so sein sollte, alle Instanzen werden gesperrt, wenn eine Instanz geklont wird!
Josh M.

11

Selbst die akzeptierte Antwort ist ConcurrentBag. Ich denke nicht, dass dies in allen Fällen eine echte Ersetzung der Liste darstellt, da Radeks Kommentar zur Antwort lautet: "ConcurrentBag ist eine ungeordnete Sammlung, daher garantiert sie im Gegensatz zu List keine Bestellung. Außerdem können Sie nicht auf Artikel nach Index zugreifen." ".

Wenn Sie also .NET 4.0 oder höher verwenden, besteht eine Problemumgehung darin, ConcurrentDictionary mit der Ganzzahl TKey als Array-Index und TValue als Array-Wert zu verwenden. Dies wird empfohlen, um die Liste im C # Concurrent Collections-Kurs von Pluralsight zu ersetzen . ConcurrentDictionary löst beide oben genannten Probleme: Indexzugriff und -reihenfolge (wir können uns nicht auf die Reihenfolge verlassen, da es sich um eine Hash-Tabelle unter der Haube handelt, aber die aktuelle .NET-Implementierung spart die Reihenfolge des Hinzufügens von Elementen).


1
Bitte geben Sie Gründe für -1
tytyryty

Ich habe nicht herabgestimmt und es gibt keinen Grund dafür, IMO. Sie haben Recht, aber das Konzept wird bereits in einigen Antworten erwähnt. Für mich war der Punkt, dass es in .NET 4.0 eine neue thread-sichere Sammlung gibt, die mir nicht bekannt war. Nicht sicher, gebrauchte Tasche oder Sammlung für die Situation. +1
Xaqron

2
Diese Antwort hat mehrere Probleme: 1) ConcurrentDictionaryist ein Wörterbuch, keine Liste. 2) Es wird nicht garantiert, dass die Reihenfolge beibehalten wird, wie in Ihrer eigenen Antwort angegeben, was Ihrem angegebenen Grund für die Veröffentlichung einer Antwort widerspricht. 3) Es wird auf ein Video verlinkt, ohne die entsprechenden Zitate in diese Antwort aufzunehmen (was möglicherweise ohnehin nicht mit ihrer Lizenzierung übereinstimmt).
jpmc26

Sie können sich nicht auf Dinge verlassen, die in current implementationder Dokumentation nicht ausdrücklich garantiert sind. Die Implementierung kann jederzeit ohne vorherige Ankündigung geändert werden.
Victor Yarema

@ jpmc26, ja, es ist natürlich kein vollständiger Ersatz für List, aber das Gleiche gilt für ConcurrentBag als akzeptierte Antwort - es ist kein strikter Ersatz für List, sondern umgeht es. Um auf Ihre Bedenken zu antworten: 1) ConcurrentDictionary ist ein Wörterbuch, keine Liste, für die Sie Recht haben. Die Liste enthält jedoch ein Array, das in O (1) wie das Wörterbuch mit int als Schlüssel indiziert werden kann. 2) Ja, die Reihenfolge wird von doc (nicht garantiert) obwohl es erhalten bleibt), aber akzeptiert ConcurrentBag kann auch in Multithread-Szenarien keine Ordnung garantieren
tytyryty

9

Die ArrayListKlasse von C # hat eine SynchronizedMethode.

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

Dies gibt einen thread-sicheren Wrapper um jede Instanz von zurück IList. Alle Vorgänge müssen über den Wrapper ausgeführt werden, um die Gewindesicherheit zu gewährleisten.


1
Von welcher Sprache sprichst du?
John Demetriou

Java? Eine der wenigen Funktionen, die ich vermisse. Aber es wird normalerweise geschrieben als: Collections.synchronizedList (new ArrayList ());
Nick

2
Dies ist ein gültiges C #, vorausgesetzt, Sie verwenden System.Collections oder var System.Collections.ArrayList.Synchronized (neues System.Collections.ArrayList ()).
user2163234

5

Wenn Sie sich den Quellcode für List of T ansehen ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), werden Sie feststellen, dass dort eine Klasse vorhanden ist (was natürlich der Fall ist) intern - warum, Microsoft, warum?!?!) namens SynchronizedList of T. Ich kopiere den Code hier und füge ihn ein:

   [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);
            }
        }
    }

Persönlich denke ich, dass sie wussten, dass eine bessere Implementierung mit SemaphoreSlim erstellt werden kann, aber nicht dazu gekommen sind.


2
+1 Das Sperren der gesamten Sammlung ( _root) bei jedem Zugriff (Lesen / Schreiben) macht dies zu einer langsamen Lösung. Vielleicht ist es besser für diese Klasse, intern zu bleiben.
Xaqron

3
Diese Implementierung ist nicht threadsicher. Es wird immer noch "System.InvalidOperationException: 'Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt.'"
Raman Zhylich

2
Dies hängt nicht mit der Thread-Sicherheit zusammen, sondern mit der Tatsache, dass Sie die Sammlung iterieren und ändern. Die Ausnahme wird vom Enumerator ausgelöst, wenn er sieht, dass die Liste geändert wurde. Um dies zu umgehen, müssen Sie Ihren eigenen IEnumerator implementieren oder den Code so ändern, dass er nicht iteriert und dieselbe Sammlung gleichzeitig ändert.
Siderite Zackwehdex

Es ist nicht Thread-sicher , da die Sammlung kann während der „synchronisiert“ Methoden geändert werden. Das ist absolut ist Teil der Thread - Sicherheit. Betrachten Sie einen Thread-Aufruf Clear()nach dem anderen, this[index]aber bevor die Sperre aktiviert wird. indexist nicht mehr sicher zu verwenden und löst eine Ausnahme aus, wenn es endgültig ausgeführt wird.
Suncat2000

2

Sie können auch das primitivere verwenden

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

welche Sperre verwendet wird (siehe diesen Beitrag C # Sperren eines Objekts, das im Sperrblock neu zugewiesen wurde ).

Wenn Sie Ausnahmen im Code erwarten, ist dies nicht sicher, aber Sie können Folgendes tun:

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");
        }
    }
}

Eines der schönen Dinge dabei ist, dass Sie die Sperre für die Dauer der Reihe von Operationen erhalten (anstatt jede Operation zu sperren). Das bedeutet, dass die Ausgabe in den richtigen Blöcken erfolgen sollte (meine Verwendung bestand darin, dass ein externer Prozess eine Ausgabe auf den Bildschirm brachte).

Ich mag die Einfachheit + Transparenz der ThreadSafeList +, die den wichtigen Beitrag zum Stoppen von Abstürzen leistet



1

Ich glaube _list.ToList(), Sie werden eine Kopie erhalten. Sie können es auch abfragen, wenn Sie Folgendes benötigen:

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

Wie auch immer, msdn sagt, dies sei in der Tat eine Kopie und nicht nur eine Referenz. Oh, und ja, Sie müssen die festgelegte Methode sperren, wie die anderen betont haben.


1

Es scheint, dass viele der Leute, die dies finden, eine thread-sichere indizierte Sammlung mit dynamischer Größe wünschen. Das Nächste und Einfachste, von dem ich weiß, wäre.

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

Dies würde erfordern, dass Sie sicherstellen, dass Ihr Schlüssel ordnungsgemäß belastet wird, wenn Sie ein normales Indizierungsverhalten wünschen. Wenn Sie vorsichtig sind, kann .count als Schlüssel für alle neuen Schlüsselwertpaare ausreichen, die Sie hinzufügen.


1
Warum sollte der Schlüssel belastet werden, wenn es nicht die Schuld des Schlüssels war?
Suncat2000

@ Suncat2000 ha!
Richard II

1

Ich würde jedem empfehlen, der sich mit List<T>Multithreading-Szenarien befasst, sich unveränderliche Sammlungen anzuschauen, insbesondere das ImmutableArray .

Ich fand es sehr nützlich, wenn Sie haben:

  1. Relativ wenige Elemente in der Liste
  2. Nicht so viele Lese- / Schreibvorgänge
  3. Viel gleichzeitiger Zugriff (dh viele Threads, die im Lesemodus auf die Liste zugreifen)

Kann auch nützlich sein, wenn Sie ein transaktionsähnliches Verhalten implementieren müssen (dh einen Einfüge- / Aktualisierungs- / Löschvorgang im Fehlerfall rückgängig machen).


-1

Hier ist die Klasse, nach der Sie gefragt haben:

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 );
                    }
                }
            }
        }
    }
}

Die Version auf Google Drive wird aktualisiert, wenn ich die Klasse aktualisiere. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous

Warum, this.GetEnumerator();wenn @Tejs vorschlägt this.Clone().GetEnumerator();?
Cœur

Warum [DataContract( IsReference = true )]?
Cœur

Die neueste Version ist jetzt auf GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous

Ich habe zwei kleine Fehler in den Add () -Methoden gefunden und behoben. Zu Ihrer Information.
Protiguous

-2

Hier ist die Klasse für die thread-sichere Liste ohne Sperre

 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;
        }
    }

Dies ist nicht threadsicher
Aldracor

-3

Grundsätzlich müssen Sie lock verwenden, wenn Sie sicher aufzählen möchten.

Bitte beziehen Sie sich hierzu auf MSDN. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Hier ist ein Teil von MSDN, an dem Sie interessiert sein könnten:

Öffentliche statische (in Visual Basic freigegebene) Mitglieder dieses Typs sind threadsicher. Es wird nicht garantiert, dass Instanzmitglieder threadsicher sind.

Eine Liste kann mehrere Leser gleichzeitig unterstützen, solange die Sammlung nicht geändert wird. Das Aufzählen durch eine Sammlung ist an sich keine threadsichere Prozedur. In dem seltenen Fall, dass eine Aufzählung mit einem oder mehreren Schreibzugriffen konkurriert, besteht die einzige Möglichkeit, die Thread-Sicherheit zu gewährleisten, darin, die Sammlung während der gesamten Aufzählung zu sperren. Damit mehrere Threads zum Lesen und Schreiben auf die Sammlung zugreifen können, müssen Sie Ihre eigene Synchronisierung implementieren.


2
Überhaupt nicht wahr. Sie können gleichzeitige Sätze verwenden.
ANeves

-15

Verwenden Sie dazu die lockAnweisung. ( Lesen Sie hier für weitere Informationen. )

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;
        }
    }
}

Zu Ihrer Information, dies ist wahrscheinlich nicht genau das, was Sie gefragt haben - Sie möchten wahrscheinlich Ihren Code weiter ausschließen, aber das kann ich nicht annehmen. Schauen Sie sich das lockSchlüsselwort an und passen Sie es an Ihre spezifische Situation an.

Wenn nötig, können Sie locksowohl im getals auch im setBlock die _listVariable verwenden, wodurch das Lesen / Schreiben nicht gleichzeitig erfolgen kann.


1
Das wird sein Problem nicht lösen; Es verhindert nur, dass Threads die Referenz festlegen und nicht zur Liste hinzufügen.
Tejs

Und was ist, wenn ein Thread den Wert festlegt, während ein anderer die Sammlung iteriert (dies ist mit Ihrem Code möglich).
Xaqron

Wie gesagt, das Schloss muss wahrscheinlich im Code weiter herausgezogen werden. Dies ist nur ein Beispiel für die Verwendung der lock-Anweisung.
Josh M.

2
@ Joel Mueller: Sicher, wenn Sie so ein dummes Beispiel herstellen. Ich versuche nur zu veranschaulichen, dass der Fragesteller in die lockAussage schauen sollte . Anhand eines ähnlichen Beispiels könnte ich argumentieren, dass wir keine for-Schleifen verwenden sollten, da Sie die Anwendung mit kaum Aufwand for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Ich habe nie behauptet, dass Ihr Code einen sofortigen Deadlock bedeutet. Es ist aus folgenden Gründen eine schlechte Antwort auf diese spezielle Frage: 1) Es schützt nicht vor dem Inhalt der Liste, der während der Aufzählung der Liste oder durch zwei Threads gleichzeitig geändert wird. 2) Das Sperren des Setters, aber nicht des Getters bedeutet, dass die Eigenschaft nicht wirklich threadsicher ist. 3) Sperren auf jeder Referenz , die von außen zugänglich ist die Klasse weit eine schlechte Praxis betrachtet wird, wie es dramatisch die Chancen des zufälligen Deadlock erhöht. Deshalb lock (this)und lock (typeof(this))sind große No-No's.
Joel Mueller
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.