Frage Thread-sichere Liste -Eigenschaft


Ich möchte eine Implementierung von List<T> als eine Eigenschaft, die ohne Zweifel fadensicher 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, ich muss eine Kopie (geklont) der Sammlung zurückgeben, also wenn wir die Sammlung iterieren und zur gleichen Zeit die Sammlung eingestellt ist, dann wird keine Ausnahme ausgelöst.

Wie Implementieren einer threadsicheren Auflistungseigenschaft?


74
2018-05-03 19:01


Ursprung


Antworten:


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

Du könntest benutzen ConcurrentBag<T> in diesem Fall statt List<T>


130
2018-05-03 19:04



Selbst wenn es die meisten Stimmen bekommen hat, kann man es normalerweise nicht nehmen System.Collections.Concurrent.ConcurrentBag<T> als thread-sicherer Ersatz für System.Collections.Generic.List<T> so wie es ist (Radek Stromský wies es bereits darauf hin) nicht bestellt.

Aber es gibt eine Klasse namens System.Collections.Generic.SynchronizedCollection<T> das ist schon seit .NET 3.0 Teil des Frameworks, aber es ist so gut versteckt an einem Ort wo man es nicht erwartet, dass es wenig bekannt ist und man wahrscheinlich nie darüber gestolpert ist (zumindest habe ich das nie getan).

SynchronizedCollection<T> wird in Assembly kompiliert System.ServiceModel.dll (Dies ist Teil des Client-Profils, aber nicht der portablen Klassenbibliothek).

Ich hoffe, das hilft.


67
2018-05-03 09:15



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

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, und daher arbeitet jede Enumeration mit einer Kopie, die während der Ausführung nicht geändert werden kann.


15
2018-05-03 19:15



Sogar angenommene Antwort ist ConcurrentBag, ich denke nicht, dass es in jedem Fall ein echter Ersatz der Liste ist, da Radeks Kommentar zu der Antwort sagt: "ConcurrentBag ist eine ungeordnete Sammlung, so dass im Gegensatz zu List keine Sortierung garantiert ist ".

Wenn Sie .NET 4.0 oder höher verwenden, könnte eine Umgehungslösung verwendet werden ConcurrentDictionary mit ganzzahligem TKey als Array-Index und TValue als Array-Wert. Dies ist eine empfohlene Methode zum Ersetzen der Liste in Pluralsight C # Concurrent Collections Kurs. ConcurrentDictionary löst beide oben genannten Probleme: Indexzugriff und -ordnung (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 speichert die Reihenfolge der hinzuzufügenden Elemente).


4
2018-03-31 18:49



Sie können verwenden:

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

Thread sichere ArrayLsit erstellen


2
2017-10-08 18:24



Sie können auch die primitiveren verwenden

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

welche Sperre verwendet (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 beispielsweise 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 in jeder Operation zu sperren). Das bedeutet, dass die Ausgabe in den richtigen Chunks ausgegeben werden sollte (meine Verwendung davon war, etwas von einem externen Prozess auf den Bildschirm zu bekommen)

Ich mag wirklich die Einfachheit + Transparenz der ThreadSafeList +, die das wichtige Bit zum Abstoppen von Abstürzen tut


1
2017-08-22 09:43



Ich glaube _list.ToList()wird dir eine Kopie machen. Sie können es auch abfragen, wenn Sie möchten:

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

Wie auch immer, msdn sagt, das ist in der Tat eine Kopie und nicht einfach eine Referenz. Oh, und ja, du wirst die Set-Methode sperren müssen, wie die anderen darauf hingewiesen haben.


0
2018-05-03 19:12



Hier ist die Klasse, nach der du gefragt hast:

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

-1
2017-12-22 22:36



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

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

Hier ist ein Teil von MSDN, an dem Sie möglicherweise interessiert sind:

Öffentliche statische Member (Shared in Visual Basic) dieses Typs sind threadsicher. Es ist nicht garantiert, dass alle Instanzmitglieder Thread-sicher 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 kein Thread-sicheres Verfahren. In dem seltenen Fall, in dem eine Aufzählung mit einem oder mehreren Schreibzugriffen konkurriert, besteht die einzige Möglichkeit, die Threadsicherheit zu gewährleisten, darin, die Sammlung während der gesamten Aufzählung zu sperren. Um der Sammlung den Zugriff durch mehrere Threads zum Lesen und Schreiben zu ermöglichen, müssen Sie eine eigene Synchronisation implementieren.


-3
2018-05-03 19:06



Benutze die lock Anweisung, dies zu tun. (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;
        }
    }
}

FYI das ist wahrscheinlich nicht genau das, was Sie fragen - Sie möchten wahrscheinlich weiter in Ihrem Code sperren, aber ich kann nicht davon ausgehen. Sieh dir die. An lock Schlüsselwort und passen Sie seinen Gebrauch an Ihre spezifische Situation an.

Wenn Sie müssen, könnten Sie lock in beiden get und set blockieren mit dem _list Variable, die es so ein Lese- / Schreib-machen würde, kann nicht zur gleichen Zeit auftreten.


-11
2018-05-03 19:04