Proprietà List <T> thread-safe


122

Voglio un'implementazione di List<T> come una proprietà che può essere utilizzata in modo sicuro senza alcun dubbio.

Qualcosa come questo:

private List<T> _list;

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

Sembra che sia ancora necessario restituire una copia (clonata) della raccolta quindi se da qualche parte stiamo iterando la raccolta e allo stesso tempo la raccolta è impostata, non viene sollevata alcuna eccezione.

Come implementare una proprietà di raccolta thread-safe?


4
usa le serrature, dovrebbe farlo.
atoMerz

È possibile utilizzare un'implementazione thread-safe di IList<T>(vs List<T>)?
Greg


Usa BlockingCollection o ConcurrentDictionary
kumar chandraketu

Quali operazioni devi fare con l'oggetto dietro la proprietà? È possibile che tu non abbia bisogno di tutto ciò che List<T>implementa? Se sì, potresti fornire un'interfaccia di cui hai bisogno invece di chiedere informazioni su tutto ciò che List<T>hai già?
Victor Yarema

Risposte:


185

Se stai prendendo di mira .Net 4 ci sono alcune opzioni in System.Collections.Concurrent Namespace

Potresti usare ConcurrentBag<T>in questo caso invece diList<T>


5
Come List <T> e diversamente da Dictionary, ConcurrentBag accetta duplicati.
The Light

115
ConcurrentBagè raccolta non ordinata, quindi a differenza di List<T>non garantisce l'ordinazione. Inoltre non puoi accedere agli elementi in base all'indice.
Radek Stromský

11
@ RadekStromský ha ragione e nel caso in cui desideri un elenco simultaneo ordinato, potresti provare ConcurrentQueue (FIFO) o ConcurrentStack (LIFO) .
Caio Cunha


12
ConcurrentBag non implementa IList e non è in realtà una versione thread-safe di List
Vasyl Zvarydchuk

87

Anche se ha ottenuto il maggior numero di voti, di solito non si può prendere System.Collections.Concurrent.ConcurrentBag<T>come sostituto thread-safe per System.Collections.Generic.List<T>ciò che è (Radek Stromský lo ha già fatto notare) non ordinato.

Ma c'è una classe chiamata System.Collections.Generic.SynchronizedCollection<T> che è già da .NET 3.0 parte del framework, ma è così ben nascosta in una posizione in cui non ci si aspetta che sia poco conosciuta e probabilmente non ci si è mai imbattuti in essa (almeno Non l'ho mai fatto).

SynchronizedCollection<T>viene compilato nell'assembly System.ServiceModel.dll (che fa parte del profilo client ma non della libreria di classi portabile).

Spero che aiuti.


3
Piango che questo non sia nel core lib: {una semplice raccolta sincronizzata è spesso tutto ciò che è necessario.
user2864740

Ulteriore discussione utile di questa opzione: stackoverflow.com/a/4655236/12484
Jon Schneider

2
È ben nascosto perché deprecato, a favore delle classi in System.Collections.Concurrent.
denfromufa

3
E non disponibile in .net core
denfromufa

2
@denfromufa sembra che abbiano aggiunto questo in .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94

17

Penso che creare una classe ThreadSafeList di esempio sarebbe facile:

public class ThreadSafeList<T> : IList<T>
{
    protected List<T> _interalList = new List<T>();

    // Other Elements of IList implementation

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

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    protected static object _lock = new object();

    public List<T> Clone()
    {
        List<T> newList = new List<T>();

        lock (_lock)
        {
            _interalList.ForEach(x => newList.Add(x));
        }

        return newList;
    }
}

È sufficiente clonare l'elenco prima di richiedere un enumeratore e quindi qualsiasi enumerazione funziona su una copia che non può essere modificata durante l'esecuzione.


1
Non è un clone superficiale? Se Tè un tipo di riferimento, non restituirà semplicemente un nuovo elenco contenente riferimenti a tutti gli oggetti originali? In tal caso, questo approccio potrebbe ancora causare problemi di threading poiché è possibile accedere agli oggetti dell'elenco da più thread tramite diverse "copie" dell'elenco.
Joel B

3
Esatto, è una copia superficiale. Il punto era semplicemente avere un set clonato che sarebbe stato sicuro di iterare (quindi newListnon ha alcun elemento aggiunto o rimosso che invaliderebbe l'enumeratore).
Tejs

7
Il _lock dovrebbe essere statico?
Mike Ward

4
Un altro pensiero. Questa implementazione è sicura per i thread per più autori? In caso contrario, forse dovrebbe essere chiamato ReadSafeList.
Mike Ward

5
@ MikeWard - Non penso che dovrebbe essere, tutte le istanze verranno bloccate quando viene clonata una qualsiasi istanza!
Josh M.

11

Anche la risposta accettata è ConcurrentBag, non credo che sia una vera sostituzione di list in tutti i casi, come dice il commento di Radek alla risposta: "ConcurrentBag è una raccolta non ordinata, quindi a differenza di List non garantisce l'ordinazione. Inoltre non è possibile accedere agli elementi per indice ".

Pertanto, se si utilizza .NET 4.0 o versioni successive, una soluzione alternativa potrebbe essere quella di utilizzare ConcurrentDictionary con il numero intero TKey come indice di matrice e TValue come valore di matrice. Questo è il modo consigliato per sostituire l'elenco nel corso C # Raccolte simultanee di Pluralsight . ConcurrentDictionary risolve entrambi i problemi sopra menzionati: accesso all'indice e ordinamento (non possiamo fare affidamento sull'ordinamento poiché è una tabella hash sotto il cofano, ma l'attuale implementazione .NET salva l'ordine degli elementi aggiunti).


1
si prega di fornire i motivi per -1
tytyryty

Non ho votato per difetto e non c'è motivo per questo IMO. Hai ragione ma il concetto è già menzionato in alcune risposte. Per me, il punto era che c'era una nuova raccolta thread-safe in .NET 4.0 di cui non ero a conoscenza. Non sono sicuro usato Borsa o Collezione per la situazione. +1
Xaqron

2
Questa risposta ha diversi problemi: 1) ConcurrentDictionaryè un dizionario, non un elenco. 2) Non è garantito il mantenimento dell'ordine, come afferma la tua risposta, il che contraddice il motivo dichiarato per la pubblicazione di una risposta. 3) Si collega a un video senza riportare le citazioni pertinenti in questa risposta (che comunque potrebbe non essere in accordo con la loro licenza).
jpmc26

Non puoi fare affidamento su cose come current implementationse non fosse esplicitamente garantito dalla documentazione. L'implementazione può cambiare in qualsiasi momento senza preavviso.
Victor Yarema

@ jpmc26, sì, ovviamente non è un sostituto completo di List, tuttavia lo stesso vale per ConcurrentBag come risposta accettata: non è una sostituzione rigorosa di List ma funziona. Per rispondere alle tue preoccupazioni: 1) ConcurrentDictionary è un dizionario non un elenco hai ragione, tuttavia l'elenco ha un array dietro, che può indicizzare in O (1) come il dizionario con int come chiave 2) sì l'ordine non è garantito da doc ( anche se è conservato), ma accettato ConcurrentBag non può garantire l'ordine anche in scenari multithread
tytyryty

9

La ArrayListclasse di C # ha un Synchronizedmetodo.

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

Ciò restituisce un wrapper thread-safe attorno a qualsiasi istanza di IList. Tutte le operazioni devono essere eseguite tramite il wrapper per garantire la sicurezza del filo.


1
Di che lingua parli?
John Demetriou

Giava? Una delle poche caratteristiche che mi mancano. Ma di solito è scritto come: Collections.synchronizedList (new ArrayList ());
Nick

2
Questo è valido C # supponendo che tu abbia un System.Collections using oppure potresti usare var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234

5

Se guardi il codice sorgente di List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ) noterai che c'è una classe lì (che ovviamente è interno - perché, Microsoft, perché?!?!) chiamato SynchronizedList of T. Sto copiando incollando il codice qui:

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

Personalmente penso che sapessero che sarebbe stata possibile creare un'implementazione migliore utilizzando SemaphoreSlim , ma non ci sono riuscita.


2
+1 Bloccare l'intera collezione ( _root) in ogni accesso (lettura / scrittura) rende questa soluzione lenta. Forse è meglio che questa classe rimanga interna.
Xaqron

3
Questa implementazione non è thread-safe. Viene comunque visualizzato "System.InvalidOperationException: 'Collection was modified; operazione di enumerazione potrebbe non essere eseguita.'"
Raman Zhylich

2
Ciò non è correlato alla sicurezza dei thread, ma al fatto che stai iterando e modificando la raccolta. L'eccezione viene lanciata dall'enumeratore quando rileva che l'elenco è stato modificato. Per aggirare questo problema è necessario implementare il proprio IEnumerator o modificare il codice in modo che non iteri e modifichi la stessa raccolta allo stesso tempo.
Siderite Zackwehdex

Non è thread-safe perché la raccolta può essere modificata durante i metodi "sincronizzati". Che assolutamente è parte del filo di sicurezza. Considera le chiamate di un thread Clear()dopo altre chiamate this[index]ma prima che il blocco venga attivato. indexnon è più sicuro da usare e genererà un'eccezione quando alla fine verrà eseguito.
Suncat2000 il

2

Puoi anche usare il più primitivo

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

quale blocco utilizza (vedere questo articolo Blocco in C # di un oggetto riassegnato in blocco blocco ).

Se ti aspetti eccezioni nel codice, questo non è sicuro ma ti consente di fare qualcosa come il seguente:

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

Una delle cose belle di questo è che otterrai il blocco per la durata della serie di operazioni (anziché il blocco in ogni operazione). Il che significa che l'output dovrebbe uscire nei blocchi giusti (il mio utilizzo di questo era ottenere un output sullo schermo da un processo esterno)

Mi piace molto la semplicità + la trasparenza di ThreadSafeList + che fa la parte importante per fermare i crash



1

Credo _list.ToList()che te ne farò una copia. Puoi anche interrogarlo se hai bisogno di come:

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

Ad ogni modo, msdn dice che questa è davvero una copia e non semplicemente un riferimento. Oh, e sì, dovrai bloccare il metodo set come hanno sottolineato gli altri.


1

Sembra che molte delle persone che lo trovano desiderino una raccolta di dimensioni dinamicamente indicizzate per thread safe. La cosa più vicina e più semplice che io sappia sarebbe.

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

Ciò richiederebbe di assicurarsi che la chiave sia correttamente incriminata se si desidera un normale comportamento di indicizzazione. Se stai attento, .count potrebbe essere sufficiente come chiave per qualsiasi nuova coppia di valori chiave che aggiungi.


1
Perché la chiave dovrebbe essere incriminata quando non era colpa della chiave?
Suncat2000 il

@ Suncat2000 ha!
Richard II,

1

Suggerirei a chiunque abbia a che fare con List<T>scenari multi-threading di dare un'occhiata alle raccolte immutabili in particolare all'ImmutableArray .

L'ho trovato molto utile quando hai:

  1. Relativamente pochi elementi nell'elenco
  2. Non così tante operazioni di lettura / scrittura
  3. MOLTI accessi simultanei (cioè molti thread che accedono alla lista in modalità di lettura)

Può anche essere utile quando è necessario implementare una sorta di comportamento simile a una transazione (cioè annullare un'operazione di inserimento / aggiornamento / eliminazione in caso di errore)


-1

Ecco il corso che hai chiesto:

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

La versione su Google Drive viene aggiornata man mano che aggiorno la classe. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguo

Perché this.GetEnumerator();quando @Tejs suggerisce this.Clone().GetEnumerator();?
Cœur

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


Ho trovato e corretto due piccoli bug nei metodi Add (). FYI.
Protiguo

-3

Fondamentalmente, se vuoi enumerare in modo sicuro, devi usare lock.

Fare riferimento a MSDN su questo. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Ecco una parte di MSDN che potrebbe interessarti:

I membri statici pubblici (condivisi in Visual Basic) di questo tipo sono thread-safe. Non è garantito che tutti i membri dell'istanza siano thread-safe.

Un elenco può supportare più lettori contemporaneamente, a condizione che la raccolta non venga modificata. L'enumerazione tramite una raccolta non è intrinsecamente una procedura thread-safe. Nel raro caso in cui un'enumerazione contenga uno o più accessi in scrittura, l'unico modo per garantire la sicurezza del thread è bloccare la raccolta durante l'intera enumerazione. Per consentire l'accesso alla raccolta da più thread per la lettura e la scrittura, è necessario implementare la propria sincronizzazione.


2
Non è affatto vero. È possibile utilizzare set simultanei.
ANeves

-3

Ecco la classe per l'elenco thread-safe senza blocco

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

Questo non è threadsafe
Aldracor il

_i ++ non è sicuro per i thread. devi usare un add atomico quando lo incrementi e probabilmente lo contrassegni anche come volatile. CheckReset () non è sicuro per i thread. Può succedere di tutto tra il controllo condizionale e la chiamata a Reset (). Non scrivere le tue utilità multithreading.
Chris Rollins

-15

Usa l' lockistruzione per farlo. ( Leggi qui per maggiori informazioni. )

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

Cordiali saluti, questo probabilmente non è esattamente ciò che stai chiedendo: probabilmente vorrai bloccare più a fondo il tuo codice, ma non posso presumerlo. Dai un'occhiata alla lockparola chiave e personalizzane l'uso in base alla tua situazione specifica.

Se è necessario, si potrebbe locksia in gete setblocco utilizzando la _listvariabile che renderebbe così una lettura / scrittura non può avvenire allo stesso tempo.


1
Questo non risolverà il suo problema; impedisce solo ai thread di impostare il riferimento, non di aggiungerlo all'elenco.
Tejs

E se un thread sta impostando il valore mentre un altro sta iterando la raccolta (è possibile con il tuo codice).
Xaqron

Come ho detto, probabilmente il lucchetto dovrà essere spostato ulteriormente nel codice. Questo è solo un esempio di come utilizzare l'istruzione lock.
Josh M.

2
@ Joel Mueller: Certo, se produci qualche stupido esempio come quello. Sto solo cercando di illustrare che il richiedente dovrebbe esaminare la lockdichiarazione. Usando un esempio simile, potrei sostenere che non dovremmo usare i cicli for poiché potresti bloccare l'applicazione con pochissimo sforzo:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.

5
Non ho mai affermato che il tuo codice significasse un deadlock istantaneo. È una cattiva risposta a questa particolare domanda per i seguenti motivi: 1) Non protegge dal contenuto dell'elenco che viene modificato durante l'enumerazione dell'elenco, o da due thread contemporaneamente. 2) Bloccare il setter ma non il getter significa che la proprietà non è realmente thread-safe. 3) Il blocco su qualsiasi riferimento accessibile dall'esterno della classe è ampiamente considerato una cattiva pratica, poiché aumenta notevolmente le possibilità di deadlock accidentale. Ecco perché lock (this)e lock (typeof(this))sono grandi no-no.
Joel Mueller
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.