Właściwość List <T> bezpieczna wątkowo


122

Chcę implementacji List<T>jako właściwości, której można bez wątpienia bezpiecznie używać.

Coś takiego:

private List<T> _list;

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

Wygląda na to, że nadal muszę zwrócić kopię (sklonowaną) kolekcji, więc jeśli gdzieś iterujemy kolekcję i w tym samym czasie kolekcja jest ustawiona, nie jest zgłaszany żaden wyjątek.

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


4
użyj zamków, to powinno wystarczyć.
atoMerz

Czy można użyć bezpiecznej wątkowo implementacji IList<T>(vs List<T>)?
Greg,


Użyj BlockingCollection lub ConcurrentDictionary
kumar chandraketu

Jakie operacje musisz wykonać na obiekcie za nieruchomością? Czy to możliwe, że nie potrzebujesz wszystkiego, co List<T>wdraża? Jeśli tak, czy możesz podać interfejs, którego potrzebujesz, zamiast pytać o wszystko, co List<T>już masz?
Victor Yarema

Odpowiedzi:


185

Jeśli celujesz w .Net 4, w przestrzeni nazw System.Collections.Concurrent jest kilka opcji

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


5
Podobnie jak List <T> iw przeciwieństwie do Dictionary, ConcurrentBag akceptuje duplikaty.
The Light

115
ConcurrentBagjest to zbiór nieuporządkowany, więc w przeciwieństwie do List<T>niego nie gwarantuje zamówienia. Nie możesz również uzyskać dostępu do elementów według indeksu.
Radek Stromský

11
@ RadekStromský ma rację, a jeśli chcesz uporządkować listę współbieżną, możesz spróbować ConcurrentQueue (FIFO) lub ConcurrentStack (LIFO) .
Caio Cunha


12
ConcurrentBag nie implementuje IList i nie jest w rzeczywistości wersją listy bezpieczną dla wątków
Vasyl Zvarydchuk

87

Mimo że uzyskał najwięcej głosów, zwykle nie można traktować System.Collections.Concurrent.ConcurrentBag<T>jako bezpiecznego zamiennika tego, System.Collections.Generic.List<T>co jest (zwrócił na to już Radek Stromský), którego nie zamówiono.

Ale istnieje klasa o nazwie, System.Collections.Generic.SynchronizedCollection<T>która istnieje już od czasu części .NET 3.0 frameworka, ale jest tak dobrze ukryta w miejscu, w którym nie można się tego spodziewać, że jest mało znana i prawdopodobnie nigdy się o nią nie natknąłeś (przynajmniej Nigdy nie zrobiłem).

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

Mam nadzieję, że to pomoże.


3
Płaczę, że nie ma tego w core lib: {Często wystarczy prosta zsynchronizowana kolekcja.
user2864740

Dodatkowa pomocna dyskusja na temat tej opcji: stackoverflow.com/a/4655236/12484
Jon Schneider

2
Jest dobrze ukryty, ponieważ jest przestarzały, na korzyść klas w System.Collections.Concurrent.
denfromufa

3
I niedostępny w .net core
denfromufa

2
@denfromufa wygląda na to, że dodali to w .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/ ...
Cirelli94

17

Myślę, że stworzenie przykładowej klasy ThreadSafeList byłoby ł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 klonujesz listę przed zażądaniem modułu wyliczającego, a zatem każde wyliczenie działa na kopii, której nie można modyfikować podczas działania.


1
Czy to nie jest płytki klon? Jeśli Tjest to typ referencyjny, czy to nie zwróci po prostu nowej listy zawierającej odniesienia do wszystkich oryginalnych obiektów? Jeśli tak jest, to podejście może nadal powodować problemy z wątkami, ponieważ obiekty listy mogą być dostępne przez wiele wątków za pośrednictwem różnych „kopii” listy.
Joel B

3
Prawidłowo, to płytka kopia. Chodziło o to, aby po prostu mieć sklonowany zestaw, który byłby bezpieczny do iteracji (więc newListnie ma żadnych dodanych ani usuniętych elementów, które unieważniłyby moduł wyliczający).
Tejs

7
Czy _lock powinien być statyczny?
Mike Ward

4
Kolejna myśl. Czy ta implementacja jest bezpieczna wątkowo dla wielu autorów? Jeśli nie, może powinno nazywać się ReadSafeList.
Mike Ward

5
@MikeWard - nie sądzę, aby tak było, wszystkie wystąpienia zostaną zablokowane podczas klonowania dowolnej instancji!
Josh M.,

11

Nawet akceptowana odpowiedź to ConcurrentBag, nie sądzę, że jest to realne zastąpienie listy we wszystkich przypadkach, ponieważ komentarz Radka do odpowiedzi mówi: „ConcurrentBag jest kolekcją nieuporządkowaną, więc w przeciwieństwie do List nie gwarantuje zamówienia. Nie można również uzyskać dostępu do pozycji według indeksu ”.

Więc jeśli używasz .NET 4.0 lub nowszego, obejściem może być użycie ConcurrentDictionary z liczbą całkowitą TKey jako indeksem tablicy i TValue jako wartością tablicy. Jest to zalecany sposób zastępowania listy w kursie kolekcji współbieżnych języka C # Pluralsight . ConcurrentDictionary rozwiązuje oba wspomniane powyżej problemy: dostęp do indeksu i porządkowanie (nie możemy polegać na porządkowaniu, ponieważ jest to tablica mieszająca pod maską, ale obecna implementacja .NET oszczędza kolejność dodawania elementów).


1
proszę podać powody -1
tytyryty

Nie głosowałem negatywnie i nie ma powodu, by to robić 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 bezpiecznych wątków, o której nie wiedziałem. Nie jestem pewien, czy używana torba lub kolekcja w tej sytuacji. +1
Xaqron

2
Ta odpowiedź ma kilka problemów: 1) ConcurrentDictionaryjest słownikiem, a nie listą. 2) Nie ma gwarancji zachowania porządku, jak stwierdza Twoja własna odpowiedź, co jest sprzeczne z podanym przez Ciebie powodem opublikowania odpowiedzi. 3) Odsyła do filmu bez umieszczania odpowiednich cytatów w tej odpowiedzi (co i tak może nie być zgodne z licencją).
jpmc26

Nie możesz polegać na takich rzeczach, jak current implementationjeśli nie jest to wyraźnie zagwarantowane w dokumentacji. Wdrożenie może ulec zmianie w dowolnym czasie bez powiadomienia.
Victor Yarema

@ jpmc26, tak, oczywiście nie jest to pełny zamiennik listy, ale to samo dotyczy ConcurrentBag jako akceptowanej odpowiedzi - nie jest to ścisłe zastąpienie listy, ale obejście. Odpowiadając na twoje obawy: 1) ConcurrentDictionary jest słownikiem, a nie listą masz rację, jednak lista zawiera tablicę, która może indeksować w O (1) tak samo jak słownik z int jako klucz 2) tak kolejność nie jest gwarantowana przez doc ( chociaż jest zachowany), ale zaakceptowany ConcurrentBag nie może zagwarantować porządku również w scenariuszach wielowątkowych
tytyryty

9

ArrayListKlasa C # ma Synchronizedmetodę.

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

Zwraca to bezpieczne opakowanie dla każdego wystąpienia IList. Wszystkie operacje muszą być wykonywane przez owijkę, aby zapewnić bezpieczeństwo nici.


1
O jakim języku mówisz?
John Demetriou

Jawa? Jedna z niewielu funkcji, których mi brakuje. Ale zwykle jest zapisywany jako: Collections.synchronizedList (new ArrayList ());
Nick

2
Jest to poprawne C # przy założeniu, że masz using System.Collections lub możesz użyć var ​​System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234

5

Jeśli spojrzysz na kod źródłowy listy T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), zauważysz, że jest tam klasa (co oczywiście jest wewnętrzny - dlaczego Microsoft, dlaczego?!?!) o nazwie SynchronizedList of T. Kopiuję wklejając kod tutaj:

   [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 wiedzieli, że można stworzyć lepszą implementację przy użyciu SemaphoreSlim , ale nie do końca do tego doszli.


2
+1 Blokowanie całej kolekcji ( _root) w każdym dostępie (odczyt / zapis) sprawia, że ​​jest to powolne rozwiązanie. Może lepiej, żeby ta klasa pozostała wewnętrzna.
Xaqron,

3
Ta implementacja nie jest bezpieczna wątkowo. Nadal wyrzuca „System.InvalidOperationException: 'Kolekcja została zmodyfikowana; operacja wyliczenia może nie zostać wykonana.'”
Raman Zhylich

2
Nie jest to związane z bezpieczeństwem wątków, ale z faktem, że wykonujesz iterację i zmieniasz kolekcję. Wyjątek jest generowany przez moduł wyliczający, gdy widzi, że lista została zmieniona. Aby obejść ten problem, musisz zaimplementować własny IEnumerator lub zmienić kod, aby nie iterował i nie zmieniał tej samej kolekcji w tym samym czasie.
Siderite Zackwehdex

Nie jest bezpieczny dla wątków, ponieważ kolekcję można zmienić za pomocą metod „zsynchronizowanych”. To jest absolutnie częścią bezpieczeństwa nici. Rozważ jedno wywołanie wątku Clear()po innych wywołaniach, this[index]ale przed aktywacją blokady. indexnie jest już bezpieczny w użyciu i zgłosi wyjątek, gdy zostanie ostatecznie wykonany.
Suncat2000

2

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

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

która używa blokady (zobacz ten post C # Blokowanie obiektu, który jest ponownie przypisywany w bloku blokady ).

Jeśli spodziewasz się wyjątków w kodzie, nie jest to bezpieczne, ale pozwala 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 wychodzić w odpowiednich fragmentach (moje użycie tego polegało na wyświetlaniu na ekranie niektórych danych wyjściowych z zewnętrznego procesu)

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



1

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

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

W każdym razie msdn twierdzi, że to rzeczywiście kopia, a nie tylko odniesienie. Aha, i tak, będziesz musiał zablokować ustaloną metodę, jak wskazali inni.


1

Wygląda na to, że wiele osób, które to znalazły, chce mieć kolekcję o dynamicznych rozmiarach, bezpieczną dla wątków i indeksowaną. Najbliższa i najłatwiejsza rzecz, jaką znam, byłaby.

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

Wymagałoby to upewnienia się, że klucz jest odpowiednio obciążony, jeśli chcesz normalnego zachowania podczas indeksowania. Jeśli zachowasz ostrożność, .count może wystarczyć jako klucz dla każdej dodawanej nowej pary klucz-wartość.


1
Dlaczego klucz miałby być obciążony, skoro to nie była wina klucza?
Suncat2000

@ Suncat2000 ha!
Richard II

1

Sugerowałbym każdemu, kto ma do czynienia ze List<T>scenariuszami wielowątkowymi, aby przyjrzał się niezmiennym kolekcjom, w szczególności ImmutableArray .

Uważam, że jest to bardzo przydatne, gdy masz:

  1. Stosunkowo niewiele pozycji na liście
  2. Niewiele operacji odczytu / zapisu
  3. DUŻO jednoczesnego dostępu (tj. Wiele wątków, które uzyskują dostęp do listy w trybie czytania)

Może być również przydatny, gdy musisz zaimplementować zachowanie podobne do transakcji (tj. Cofnąć operację wstawiania / aktualizowania / usuwania w przypadku niepowodzenia)


-1

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

Wersja na Dysku Google jest aktualizowana, gdy aktualizuję zajęcia. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous

Dlaczego, this.GetEnumerator();kiedy @Tejs sugeruje this.Clone().GetEnumerator();?
Cœur,

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

Najnowsza wersja jest już dostępna na GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous

Znalazłem i naprawiłem dwa małe błędy w metodach Add (). FYI.
Protiguous

-3

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

Więcej informacji można znaleźć w witrynie MSDN. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

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

Publiczne statyczne elementy członkowskie tego typu (udostępnione w języku Visual Basic) są bezpieczne wątkowo. Nie ma gwarancji, że żadne elementy członkowskie instancji będą bezpieczne dla wątków.

Lista może obsługiwać wielu czytelników jednocześnie, o ile kolekcja nie jest modyfikowana. Wyliczanie w kolekcji nie jest wewnętrznie procedurą bezpieczną dla wątków. W rzadkich przypadkach, gdy wyliczenie rywalizuje z co najmniej jednym dostępem do zapisu, jedynym sposobem zapewnienia bezpieczeństwa wątków jest zablokowanie kolekcji podczas całego wyliczania. Aby umożliwić dostęp do kolekcji przez wiele wątków w celu odczytu i zapisu, należy zaimplementować własną synchronizację.


2
Wcale nieprawda. Możesz używać zestawów współbieżnych.
ANeves

-3

Oto klasa listy bezpiecznych wątków bez blokady

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

To nie jest bezpieczne dla
wątków

_i ++ nie obsługuje wątków. musisz użyć dodatku atomowego, kiedy go zwiększasz i prawdopodobnie oznaczysz go również jako lotny. CheckReset () nie obsługuje wątków. Cokolwiek może się zdarzyć pomiędzy sprawdzeniem warunkowym a wywołaniem Reset (). Nie pisz własnych narzędzi wielowątkowych.
Chris Rollins

-15

Użyj do tego lockinstrukcji. ( Przeczytaj tutaj, aby uzyskać więcej informacji. )

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, prawdopodobnie nie jest to dokładnie to, o co prosisz - prawdopodobnie chcesz zablokować dalej w swoim kodzie, ale nie mogę tego założyć. Przyjrzyj się locksłowu kluczowemu i dostosuj jego użycie do swojej konkretnej sytuacji.

Jeśli trzeba, można lockw obu geti setbloku przy użyciu _listzmiennej, która stałaby się więc odczytu / zapisu nie mogą występować jednocześnie.


1
To nie rozwiąże jego problemu; powstrzymuje tylko wątki od ustawiania odniesienia, a nie dodawania do listy.
Tejs

A co, jeśli jeden wątek ustawia wartość, podczas gdy inny iteruje kolekcję (jest to możliwe z twoim kodem).
Xaqron

Jak powiedziałem, zamek prawdopodobnie będzie musiał zostać przesunięty dalej w kodzie. To tylko przykład użycia instrukcji lock.
Josh M.

2
@Joel Mueller: Jasne, jeśli wyprodukujesz taki głupi przykład. Próbuję tylko zilustrować, że pytający powinien zajrzeć do lockstwierdzenia. Korzystając z podobnego przykładu, mógłbym argumentować, że nie powinniśmy używać pętli for, ponieważ można zablokować aplikację bez żadnego wysiłku:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Nigdy nie twierdziłem, że twój kod oznaczał natychmiastowy impas. Jest to zła odpowiedź na to pytanie z następujących powodów: 1) Nie chroni przed modyfikacją zawartości listy podczas wyliczania listy lub przez dwa wątki na raz. 2) Zablokowanie ustawiającego, ale nie pobierającego, oznacza, że ​​właściwość nie jest tak naprawdę bezpieczna dla wątków. 3) Blokowanie dowolnego odniesienia, które jest dostępne spoza klasy, jest powszechnie uważane za złą praktykę, ponieważ dramatycznie zwiększa ryzyko przypadkowego impasu. Dlatego lock (this)i lock (typeof(this))są duże, nie, nie.
Joel Mueller
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.