Propiedad List <T> segura para subprocesos

122

Quiero una implementación de List<T>como una propiedad que se pueda usar de forma segura para subprocesos sin ninguna duda.

Algo como esto:

private List<T> _list;

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

Parece que todavía necesito devolver una copia (clonada) de la colección, por lo que si en algún lugar estamos iterando la colección y al mismo tiempo la colección está configurada, no se genera ninguna excepción.

¿Cómo implementar una propiedad de colección segura para subprocesos?

Xaqron
fuente
4
use cerraduras, eso debería hacerlo.
atoMerz
¿Se puede usar una implementación segura para subprocesos de IList<T>(vs List<T>)?
Greg
2
¿Ha marcado SynchronizedCollection <T> ?
Saturn Technologies
Use BlockingCollection o ConcurrentDictionary
kumar chandraketu
¿Qué operaciones necesita hacer con el objeto detrás de la propiedad? ¿Es posible que no necesite todo lo que List<T>implementa? En caso afirmativo, ¿podría proporcionar la interfaz que necesita en lugar de preguntar sobre todo lo que List<T>ya tiene?
Victor Yarema

Respuestas:

185

Si está apuntando a .Net 4, hay algunas opciones en System.Collections.Concurrent Namespace

Podrías usar ConcurrentBag<T>en este caso en lugar deList<T>

Bala R
fuente
5
Como List <T> y a diferencia de Dictionary, ConcurrentBag acepta duplicados.
The Light
115
ConcurrentBages una colección desordenada, por lo que a diferencia de List<T>ella no garantiza el pedido. Además, no puede acceder a elementos por índice.
Radek Stromský
11
@ RadekStromský tiene razón, y en el caso de que desee una lista concurrente ordenada, puede probar ConcurrentQueue (FIFO) o ConcurrentStack (LIFO) .
Caio Cunha
7
¿Quizás SynchronizedCollection <T> ?
Saturn Technologies
12
ConcurrentBag no implementa IList y en realidad no es una versión segura para subprocesos de List
Vasyl Zvarydchuk
87

Incluso cuando obtuvo la mayor cantidad de votos, por lo general no se puede tomar System.Collections.Concurrent.ConcurrentBag<T>como reemplazo seguro para subprocesos porque System.Collections.Generic.List<T>no está ordenado (Radek Stromský ya lo señaló).

Pero hay una clase llamada System.Collections.Generic.SynchronizedCollection<T>que ya es desde .NET 3.0 parte del marco, pero está tan bien escondida en una ubicación donde no se espera que sea poco conocida y probablemente nunca te hayas tropezado con ella (al menos Nunca lo hice).

SynchronizedCollection<T>se compila en el ensamblado System.ServiceModel.dll (que es parte del perfil del cliente pero no de la biblioteca de clases portátil).

Espero que ayude.

Christoph
fuente
3
Lloro porque esto no está en core lib: {Una colección sincronizada simple es a menudo todo lo que se necesita.
user2864740
Discusión adicional útil de esta opción: stackoverflow.com/a/4655236/12484
Jon Schneider
2
Está bien escondido porque está obsoleto, a favor de las clases en System.Collections.Concurrent.
denfromufa
3
Y no disponible en .net core
denfromufa
2
@denfromufa parece que agregaron esto en .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94
17

Creo que crear una clase ThreadSafeList de muestra sería fácil:

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

Simplemente clona la lista antes de solicitar un enumerador y, por lo tanto, cualquier enumeración funciona a partir de una copia que no se puede modificar mientras se ejecuta.

Tejs
fuente
1
¿No es este un clon superficial? Si Tes un tipo de referencia, ¿no devolverá simplemente una nueva lista que contiene referencias a todos los objetos originales? Si ese es el caso, este enfoque aún podría causar problemas de subprocesos, ya que varios subprocesos pueden acceder a los objetos de la lista a través de diferentes "copias" de la lista.
Joel B
3
Correcto, es una copia superficial. El punto era simplemente tener un conjunto clonado sobre el que sería seguro iterar (por lo newListque no se agregaron o eliminaron elementos que invalidaran el enumerador).
Tejs
7
¿Debería el _lock ser estático?
Mike Ward
4
Otro pensamiento. ¿Esta implementación es segura para varios escritores? Si no es así, tal vez debería llamarse ReadSafeList.
Mike Ward
5
@MikeWard: no creo que deba ser así, ¡ todas las instancias se bloquearán cuando se clone cualquier instancia!
Josh M.
11

Incluso la respuesta aceptada es ConcurrentBag, no creo que sea un reemplazo real de la lista en todos los casos, ya que el comentario de Radek a la respuesta dice: "ConcurrentBag es una colección desordenada, por lo que, a diferencia de List, no garantiza el pedido. Además, no puede acceder a los elementos por índice ".

Entonces, si usa .NET 4.0 o superior, una solución podría ser usar ConcurrentDictionary con integer TKey como índice de matriz y TValue como valor de matriz. Esta es la forma recomendada de reemplazar la lista en el curso Colecciones concurrentes de C # de Pluralsight . ConcurrentDictionary resuelve los dos problemas mencionados anteriormente: acceso al índice y ordenamiento (no podemos confiar en ordenar ya que es una tabla hash bajo el capó, pero la implementación actual de .NET ahorra el orden de adición de elementos).

tytyryty
fuente
1
por favor proporcione razones para -1
tytyryty
No voté en contra y no hay ninguna razón para ello, en mi opinión. Tiene razón, pero el concepto ya se menciona en algunas respuestas. Para mí, el punto era que hay una nueva colección segura para subprocesos en .NET 4.0 que no conocía. No estoy seguro de que haya usado una bolsa o colección para la situación. +1
Xaqron
2
Esta respuesta tiene varios problemas: 1) ConcurrentDictionaryes un diccionario, no una lista. 2) No se garantiza que se mantenga el orden, como lo establece su propia respuesta, lo que contradice su razón declarada para publicar una respuesta. 3) Se vincula a un video sin incluir las citas relevantes en esta respuesta (que de todos modos podría no estar de acuerdo con su licencia).
jpmc26
No puede confiar en cosas como current implementationsi la documentación no lo garantiza explícitamente. La implementación puede cambiar en cualquier momento sin previo aviso.
Victor Yarema
@ jpmc26, sí, no es un reemplazo completo de List, por supuesto, sin embargo, lo mismo ocurre con ConcurrentBag como respuesta aceptada: no es un reemplazo estricto de List, sino una solución alternativa. Para responder a sus inquietudes: 1) ConcurrentDictionary es un diccionario, no una lista, tiene razón, sin embargo, la lista tiene una matriz detrás, que puede indexar en O (1) lo mismo que el diccionario con int como clave 2) sí, el orden no está garantizado por doc ( aunque se conserva), pero el ConcurrentBag aceptado no puede garantizar el orden también en escenarios multiproceso
tytyryty
9

La ArrayListclase de C # tiene un Synchronizedmétodo.

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

Esto devuelve un contenedor seguro para subprocesos alrededor de cualquier instancia de IList. Todas las operaciones deben realizarse a través de la envoltura para garantizar la seguridad del hilo.

Hani Nakhli
fuente
1
¿Que idioma estas hablando?
John Demetriou
¿Java? Una de las pocas características que echo de menos. Pero generalmente está escrito como: Collections.synchronizedList (new ArrayList ());
Nick
2
Esto es válido en C # asumiendo que tiene un System.Collections usando o podría usar var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234
5

Si observa el código fuente de List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), notará que hay una clase allí (que, por supuesto, interno - ¿por qué, Microsoft, por qué?!?!) llamado SynchronizedList of T. Estoy copiando pegando el código aquí:

   [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, creo que sabían que se podía crear una mejor implementación usando SemaphoreSlim , pero no lo consiguieron.

Siderita Zackwehdex
fuente
2
+1 Bloquear toda la colección ( _root) en cada acceso (lectura / escritura) hace que esta sea una solución lenta. Quizás sea mejor que esta clase permanezca interna.
Xaqron
3
Esta implementación no es segura para subprocesos. Todavía arroja "System.InvalidOperationException: 'La colección se modificó; es posible que la operación de enumeración no se ejecute'"
Raman Zhylich
2
Eso no está relacionado con la seguridad de los subprocesos, sino con el hecho de que está iterando y cambiando la colección. El enumerador lanza la excepción cuando ve que se cambió la lista. Para evitar esto, debe implementar su propio IEnumerator o cambiar el código para que no se repita y cambie la misma colección al mismo tiempo.
Siderita Zackwehdex
No es seguro para subprocesos porque la colección se puede cambiar durante los métodos "sincronizados". Eso es absolutamente parte de la seguridad de los hilos. Considere una llamada de hilo Clear()después de otra llamada this[index]pero antes de que se active el bloqueo. indexya no es seguro de usar y lanzará una excepción cuando finalmente se ejecute.
Suncat2000
2

También puedes usar el más primitivo

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

qué bloqueo utiliza (consulte esta publicación C # Bloquear un objeto que se reasigna en el bloque de bloqueo ).

Si espera excepciones en el código, esto no es seguro, pero le permite hacer algo como lo siguiente:

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 de las cosas buenas de esto es que obtendrá el bloqueo durante la duración de la serie de operaciones (en lugar de bloquear cada operación). Lo que significa que la salida debe aparecer en los fragmentos correctos (mi uso de esto fue obtener algunos resultados en la pantalla de un proceso externo)

Realmente me gusta la simplicidad + transparencia de ThreadSafeList + que hace la parte importante para detener los bloqueos

JonnyRaa
fuente
2

En .NET Core (cualquier versión), puede usar ImmutableList , que tiene todas las funciones de List<T>.

JotaBe
fuente
1

Creo que _list.ToList()te haré una copia. También puede consultarlo si lo necesita, como:

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

De todos modos, msdn dice que esto es de hecho una copia y no simplemente una referencia. Ah, y sí, tendrá que bloquear el método de configuración como han señalado los demás.

Jonathan Henson
fuente
1

Parece que muchas de las personas que encuentran esto quieren una colección de tamaño dinámico indexada segura para subprocesos. Lo más cercano y fácil que conozco sería.

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

Esto requeriría que se asegure de que su clave esté debidamente incriminada si desea un comportamiento de indexación normal. Si tiene cuidado, .count podría ser suficiente como clave para cualquier nuevo par clave-valor que agregue.

usuario2163234
fuente
1
¿Por qué debería incriminarse la llave cuando no fue culpa de la llave?
Suncat2000
@ Suncat2000 ¡ja!
Ricardo II
1

Sugeriría a cualquiera que se List<T>enfrente a escenarios de subprocesos múltiples que eche un vistazo a las Colecciones inmutables, en particular a ImmutableArray .

Lo he encontrado muy útil cuando tienes:

  1. Relativamente pocos elementos en la lista
  2. No tantas operaciones de lectura / escritura
  3. MUCHO acceso concurrente (es decir, muchos hilos que acceden a la lista en modo lectura)

También puede ser útil cuando necesita implementar algún tipo de comportamiento similar a una transacción (es decir, revertir una operación de inserción / actualización / eliminación en caso de error)

adospace
fuente
-1

Aquí está la clase que solicitó:

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 );
                    }
                }
            }
        }
    }
}
Protiguo
fuente
La versión en Google Drive se actualiza a medida que actualizo la clase. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous
¿Por qué this.GetEnumerator();cuando @Tejs sugiere this.Clone().GetEnumerator();?
Cœur
¿Por qué [DataContract( IsReference = true )]?
Cœur
¡La última versión está ahora en GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous
Encontré y solucioné dos pequeños errores en los métodos Add (). FYI.
Protiguous
-3

Básicamente, si desea enumerar de forma segura, debe usar lock.

Consulte MSDN sobre esto. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Aquí hay parte de MSDN que podría interesarle:

Los miembros públicos estáticos (compartidos en Visual Basic) de este tipo son seguros para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos.

Una lista puede admitir varios lectores al mismo tiempo, siempre que no se modifique la colección. Enumerar a través de una colección no es intrínsecamente un procedimiento seguro para subprocesos. En el raro caso en el que una enumeración compite con uno o más accesos de escritura, la única forma de garantizar la seguridad de los subprocesos es bloquear la colección durante toda la enumeración. Para permitir que varios subprocesos accedan a la colección para leer y escribir, debe implementar su propia sincronización.

istudy0
fuente
2
No es cierto del todo. Puede utilizar conjuntos concurrentes.
ANeves
-3

Aquí está la clase para la lista segura para subprocesos sin bloqueo

 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;
        }
    }
Suraj Mangal
fuente
Esto no es seguro para subprocesos
Aldracor
_i ++ no es seguro para subprocesos. tienes que usar una adición atómica cuando la incrementas y probablemente la marques como volátil también. CheckReset () no es seguro para subprocesos. Cualquier cosa puede suceder entre la verificación condicional y la llamada a Reset (). No escriba sus propias utilidades de subprocesos múltiples.
Chris Rollins
-15

Utilice la lockdeclaración para hacer esto. ( Lea aquí para obtener más información ) .

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

Para su información, esto probablemente no sea exactamente lo que está preguntando; es probable que desee bloquear más en su código, pero no puedo asumir eso. Eche un vistazo allock palabra clave y adapte su uso a su situación específica.

Si es necesario, puede locken ambos bloques gety setusando la _listvariable que haría que no se pueda realizar una lectura / escritura al mismo tiempo.

Josh M.
fuente
1
Eso no va a resolver su problema; solo evita que los subprocesos establezcan la referencia, no agreguen a la lista.
Tejs
¿Y qué pasa si un hilo establece el valor mientras otro itera la colección (es posible con su código)?
Xaqron
Como dije, es probable que el candado deba moverse más hacia afuera en el código. Este es solo un ejemplo de cómo usar la declaración de bloqueo.
Josh M.
2
@Joel Mueller: Claro, si fabrica algún ejemplo tonto como ese. Solo estoy tratando de ilustrar que el autor de la pregunta debe examinar la lockdeclaración. Usando un ejemplo similar, podría argumentar que no deberíamos usar bucles for, ya que podría bloquear la aplicación sin apenas esfuerzo:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.
5
Nunca reclamé que tu código significara un punto muerto instantáneo. Es una mala respuesta a esta pregunta en particular por las siguientes razones: 1) No protege contra la modificación del contenido de la lista durante la enumeración de la lista, o por dos hilos a la vez. 2) Bloquear el setter pero no el getter significa que la propiedad no es realmente segura para subprocesos. 3) Bloquear cualquier referencia que sea accesible desde fuera de la clase se considera una mala práctica, ya que aumenta drásticamente las posibilidades de un punto muerto accidental. Es por eso que lock (this)y lock (typeof(this))son grandes no-no.
Joel Mueller