Pytanie Właściwość listy wątków


Chcę wdrożenia List<T> jako własność, która może być bez wątpienia używana bez żadnych problemów.

Coś takiego:

private List<T> _list;

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

Wydaje się, że nadal potrzebuję zwrócić kopię (sklonowaną) kolekcji, więc jeśli gdzieś będziemy iterować kolekcję i jednocześnie zostanie ustawiona kolekcja, to nie zostanie zgłoszony żaden wyjątek.

Jak zaimplementować właściwość kolekcji bezpieczną dla wątków?


76
2018-05-03 19:01


pochodzenie


użyj zamków, które powinny to zrobić. - atoMerz
Można użyć implementacji bezpiecznej dla wątków IList<T> (vs List<T>)? - Greg
Czy sprawdziłeś SynchronizedCollection <T> ? - Saturn Technologies


Odpowiedzi:


Jeśli celujesz w .Net 4, istnieje kilka opcji System.Collections.Concurrent Przestrzeń nazw

Możesz użyć ConcurrentBag<T> w tym przypadku zamiast List<T>


131
2018-05-03 19:04



Podobnie jak lista <T> i w przeciwieństwie do słownika, ConcurrentBag przyjmuje duplikaty. - The Light
ConcurrentBag jest kolekcją nieuporządkowaną, więc w przeciwieństwie do List<T> nie gwarantuje zamówienia. Ponadto nie można uzyskać dostępu do elementów według indeksu. - Radek Stromský
@ RadekStromský ma rację, a jeśli chcesz mieć uporządkowaną listę równoległą, możesz spróbować ConcurrentQueue (FIFO) lub ConcurrentStack (LIFO). - Caio Cunha
Może SynchronizedCollection <T> ? - Saturn Technologies
ConcurrentBag nie implementuje IList i w rzeczywistości nie jest wersją listy bezpiecznej dla wątków - Vasyl Zvarydchuk


Nawet gdy ma najwięcej głosów, zwykle nie można tego zrobić System.Collections.Concurrent.ConcurrentBag<T> jako bezpieczny dla wątków zamiennik System.Collections.Generic.List<T> jak to jest (Radek Stromský już to wskazał) nie zamówione.

Ale istnieje klasa zwana System.Collections.Generic.SynchronizedCollection<T> to już od .NET 3.0 część struktury, ale jest dobrze ukryta w miejscu, gdzie nikt się nie spodziewa, że ​​jest mało znany i prawdopodobnie nigdy nie natknąłeś się na to (przynajmniej nigdy tego nie robiłem).

SynchronizedCollection<T> jest kompilowany do montażu System.ServiceModel.dll (który jest częścią profilu klienta, ale nie przenośnej biblioteki klas).

Nadzieja, która pomaga.


68
2018-05-03 09:15



Dobra podpowiedź. Nadal przenośność ma duże znaczenie. - Xaqron
Płaczę, że nie jest to w podstawowej bibliotece: {Prosta zsynchronizowana kolekcja jest często wszystkim, co jest potrzebne. - user2864740
Dodatkowe pomocne omówienie tej opcji: stackoverflow.com/a/4655236/12484 - Jon Schneider
Jest dobrze ukryty, ponieważ jest przestarzały, na rzecz klas w System.Collections.Concurrent. - denfromufa
I niedostępne w rdzeniu .net - denfromufa


Myślę, że robienie próbki klasy ThreadSafeList będzie łatwe:

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

Po prostu sklonujesz listę przed żądaniem modułu wyliczającego, a zatem wyliczenie działa na kopii, której nie można zmodyfikować podczas działania.


15
2018-05-03 19:15



Czy to nie jest płytki klon? Gdyby T jest typem odniesienia, czy nie po prostu zwróci nową listę zawierającą odniesienia do wszystkich oryginalnych obiektów? W takim przypadku to podejście może nadal powodować problemy z wątkami, ponieważ dostęp do obiektów listy można uzyskać przez wiele wątków za pośrednictwem różnych "kopii" listy. - Joel B
Prawidłowo, jest to płytka kopia. Chodziło o to, aby po prostu mieć sklonowany zestaw, który byłby bezpieczny do powtórzenia (czyli newListnie ma żadnych dodanych ani usuniętych elementów, które unieważniłyby moduł wyliczający). - Tejs
Czy _lock powinien być statyczny? - Mike Ward
Kolejna myśl. Czy ta implementacja jest bezpieczna dla wielu pisarzy? Jeśli nie, może powinien być nazywany ReadSafeList. - Mike Ward
@MikeWard - Nie sądzę, żeby tak było, wszystko wystąpienie zostanie zablokowane, gdy każdy instancja jest klonowana! - Josh M.


Nawet akceptowaną odpowiedzią jest ConcurrentBag, nie sądzę, że jest to prawdziwe zastąpienie listy we wszystkich przypadkach, ponieważ komentarz Radka do odpowiedzi brzmi: "ConcurrentBag jest kolekcją nieuporządkowaną, więc w przeciwieństwie do listy nie gwarantuje porządku, a także nie można uzyskać dostępu do elementów według indeksu ".

Więc jeśli używasz .NET 4.0 lub nowszego, można użyć obejścia ConcurrentDictionary z liczbą całkowitą TKey jako indeksem tablicy i TValue jako wartością tablicy. Jest to zalecany sposób zamiany listy w Pluralsight Kurs C # Concurrent Collections. ConcurrentDictionary rozwiązuje oba problemy wymienione powyżej: dostęp do indeksu i porządkowanie (nie możemy polegać na zamówieniu, ponieważ jest to tabela mieszania pod maską, ale obecna implementacja .NET zapisuje kolejność dodawania elementów).


4
2018-03-31 18:49



proszę podać powody -1 - tytyryty
Nie głosowałem w dół i nie ma żadnego powodu dla IMO. Masz rację, ale koncepcja jest już wspomniana w niektórych odpowiedziach. Dla mnie chodziło o to, że w .NET 4.0 jest nowa kolekcja bezpieczna dla wątków, o której nie wiedziałem. Nie masz pewności, czy w danej sytuacji używana była torba lub kolekcja. +1 - Xaqron
dziękuję Xaqron! - tytyryty
Ta odpowiedź ma kilka problemów: 1) ConcurrentDictionary jest słownikiem, a nie listą. 2) Nie ma gwarancji, aby zachować porządek, jak stwierdza twoja własna odpowiedź, co przeczy twojemu uzasadnionemu powodowi wysłania odpowiedzi. 3) Łączy się z a wideo bez wprowadzania odpowiednich odpowiedzi do tej odpowiedzi (które i tak mogą nie być zgodne z ich licencjami). - jpmc26


Możesz użyć:

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

utworzyć wątek Safe ArrayLsit


2
2017-10-08 18:24



O jakim języku mówisz? - John Demetriou
Jawa? Jedną z niewielu funkcji, za którymi tęsknię. Ale zwykle jest napisane jako: Collections.synchronizedList (new ArrayList ()); - Nick


Możesz także użyć bardziej prymitywnego

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

która blokada używa (zobacz ten post C # Blokowanie obiektu, który został ponownie przypisany w bloku blokującym).

Jeśli spodziewasz się wyjątków w kodzie, nie jest to bezpieczne, ale pozwala ci zrobić coś takiego:

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

Jedną z fajnych rzeczy jest to, że otrzymasz blokadę na czas trwania serii operacji (zamiast blokowania w każdej operacji). Co oznacza, że ​​dane wyjściowe powinny wyjść we właściwych porcjach (moje użycie tego spowodowało, że niektóre dane wyjściowe pojawiły się na ekranie z zewnętrznego procesu)

Naprawdę podoba mi się prostota + przezroczystość ThreadSafeList +, która odgrywa ważną rolę w zatrzymywaniu awarii


1
2017-08-22 09:43





Jeśli spojrzysz na kod źródłowy listy T (https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877) zauważysz, że istnieje tam klasa (która jest oczywiście wewnętrzna - dlaczego, Microsoft, dlaczego?!?!) nazywa się SynchronizedList of T. Kopiuję tutaj poniższy kod:

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

Osobiście uważam, że znali lepszą implementację przy użyciu SemaphoreSlim mógł zostać stworzony, ale go nie osiągnął.


1
2017-08-12 19:46



+1 Blokowanie całej kolekcji (_root) w każdym dostępie (odczyt / zapis) czyni to powolnym rozwiązaniem. Może lepiej, aby ta klasa pozostała wewnętrzna. - Xaqron


wierzę _list.ToList() zrobi ci kopię. Możesz również zapytać go, jeśli potrzebujesz, na przykład:

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

W każdym razie, msdn mówi, że to jest rzeczywiście kopia, a nie tylko odnośnik. Aha, i tak, będziesz musiał zablokować ustawioną metodę, jak wskazali inni.


0
2018-05-03 19:12





Oto klasa, o którą prosiłeś:

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



opublikuj aktualny kod - Cole Johnson
Wersja na Dysku Google zostanie zaktualizowana podczas aktualizacji klasy. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html - Protiguous
Czemu this.GetEnumerator(); gdy sugeruje @Tejs this.Clone().GetEnumerator();? - Cœur
Czemu [DataContract( IsReference = true )]? - Cœur
Najnowsza wersja jest teraz na GitHub! github.com/AIBrain/Librainian/blob/master/Collections/... - Protiguous


Zasadniczo, jeśli chcesz bezpiecznie wyliczyć, musisz użyć blokady.

Proszę zapoznać się z MSDN na ten temat. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Oto część MSDN, która może Cię zainteresować:

Publiczne statyczne (Shared in Visual Basic) elementy tego typu są bezpieczne dla wątków. Dowolni członkowie instancji nie mogą zagwarantować bezpieczeństwa w wątku.

Lista może jednocześnie obsługiwać wiele czytników, o ile kolekcja nie jest modyfikowana. Wyliczanie poprzez kolekcję nie jest samoistnie procedurą bezpieczną dla wątków. W rzadkim przypadku, gdy wyliczenie dotyczy jednego lub więcej dostępów do zapisu, jedynym sposobem zapewnienia bezpieczeństwa wątku jest zablokowanie kolekcji podczas całego wyliczenia. Aby umożliwić dostęp do kolekcji przez wiele wątków do czytania i pisania, musisz zaimplementować własną synchronizację.


-3
2018-05-03 19:06



W ogóle nie jest prawdą. Możesz użyć zestawów współbieżnych. - ANeves