Propriedade Thread-safe List <T>

122

Eu quero uma implementação de List<T>como uma propriedade que pode ser usada com segurança em thread sem qualquer dúvida.

Algo assim:

private List<T> _list;

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

Parece que ainda preciso retornar uma cópia (clonada) da coleção, portanto, se em algum lugar estivermos iterando a coleção e, ao mesmo tempo, a coleção for definida, nenhuma exceção será gerada.

Como implementar uma propriedade de coleção thread-safe?

Xaqron
fonte
4
usar bloqueios, isso deve bastar.
atoMerz
Pode usar uma implementação thread-safe de IList<T>(vs List<T>)?
Greg
2
Você verificou SynchronizedCollection <T> ?
Saturn Technologies
Use BlockingCollection ou ConcurrentDictionary
kumar chandraketu
Quais operações você precisa fazer com o objeto por trás da propriedade? É possível que você não precise de tudo o que List<T>implementa? Em caso afirmativo, você poderia fornecer uma interface de que precisa em vez de perguntar sobre tudo o que List<T>já tem?
Victor Yarema

Respostas:

185

Se você estiver direcionando .Net 4, há algumas opções no namespace System.Collections.Concurrent

Você pode usar ConcurrentBag<T>neste caso, em vez deList<T>

Bala R
fonte
5
Como List <T> e ao contrário de Dictionary, ConcurrentBag aceita duplicatas.
The Light
115
ConcurrentBagé uma coleção não ordenada, portanto, ao contrário List<T>, não garante a ordenação. Além disso, você não pode acessar itens por índice.
Radek Stromský
11
@ RadekStromský está certo, e no caso de você querer uma lista simultânea ordenada, você pode tentar ConcurrentQueue (FIFO) ou ConcurrentStack (LIFO) .
Caio Cunha
7
Talvez SynchronizedCollection <T> ?
Saturn Technologies
12
ConcurrentBag não implementa IList e não é realmente uma versão thread-safe de List
Vasyl Zvarydchuk
87

Mesmo tendo a maioria dos votos, geralmente não se pode pegar System.Collections.Concurrent.ConcurrentBag<T> como um substituto seguro para threadSystem.Collections.Generic.List<T> , pois (Radek Stromský já apontou) não ordenado.

Mas existe uma classe chamada System.Collections.Generic.SynchronizedCollection<T>que já faz parte do framework desde .NET 3.0, mas está bem escondida em um local onde não se espera que seja pouco conhecida e provavelmente você nunca tropeçou nela (pelo menos Eu nunca fiz).

SynchronizedCollection<T>é compilado no assembly System.ServiceModel.dll (que faz parte do perfil do cliente, mas não da biblioteca de classes portátil).

Espero que ajude.

Christoph
fonte
3
Eu choro que isso não está no core lib: {Uma coleção sincronizada simples é geralmente tudo o que é necessário.
user2864740
Discussão útil adicional sobre essa opção: stackoverflow.com/a/4655236/12484
Jon Schneider
2
Está bem oculto porque reprovado, em favor das classes em System.Collections.Concurrent.
denfromufa
3
E não disponível no núcleo .net
denfromufa
2
@denfromufa, parece que eles adicionaram isso ao .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94
17

Eu acho que criar uma classe ThreadSafeList de amostra seria 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;
    }
}

Você simplesmente clona a lista antes de solicitar um enumerador e, portanto, qualquer enumeração está trabalhando em uma cópia que não pode ser modificada durante a execução.

Tejs
fonte
1
Este não é um clone superficial? Se Tfor um tipo de referência, isso não retornará apenas uma nova lista contendo referências a todos os objetos originais? Se for esse o caso, essa abordagem ainda pode causar problemas de threading, pois os objetos da lista podem ser acessados ​​por vários threads por meio de diferentes "cópias" da lista.
Joel B
3
Correto, é uma cópia superficial. O objetivo era simplesmente ter um conjunto clonado que fosse seguro para iterar (portanto newList, nenhum item adicionado ou removido que invalidaria o enumerador).
Tejs
7
O _lock deve ser estático?
Mike Ward
4
Outro pensamento. Esta implementação é threadsafe para vários escritores? Caso contrário, talvez devesse ser chamado de ReadSafeList.
Mike Ward
5
@MikeWard - Não acho que deveria, todas as instâncias serão bloqueadas quando qualquer instância estiver sendo clonada!
Josh M.
11

Mesmo a resposta aceita é ConcurrentBag, não acho que seja uma substituição real de lista em todos os casos, como o comentário de Radek à resposta diz: "ConcurrentBag é uma coleção não ordenada, portanto, ao contrário de List, não garante a ordenação. Além disso, você não pode acessar itens por índice "

Portanto, se você usar o .NET 4.0 ou superior, uma solução alternativa poderia ser usar ConcurrentDictionary com o inteiro TKey como índice de array e TValue como valor de array. Esta é a maneira recomendada de substituir a lista no curso C # Coleções simultâneas da Pluralsight . ConcurrentDictionary resolve os dois problemas mencionados acima: acesso e ordenação do índice (não podemos confiar na ordenação, pois é uma tabela hash subjacente, mas a implementação atual do .NET salva a ordem de adição de elementos).

tytyryty
fonte
1
forneça os motivos para -1
tytyryty
Eu não votei contra e não há razão para isso IMO. Você tem razão, mas o conceito já foi mencionado em algumas respostas. Para mim, a questão é que existe uma nova coleção thread-safe no .NET 4.0 que eu não conhecia. Não tenho certeza se usado sacola ou coleção para a situação. +1
Xaqron
2
Essa resposta tem vários problemas: 1) ConcurrentDictionaryé um dicionário, não uma lista. 2) Não é garantido que a ordem seja preservada, como afirma sua própria resposta, o que contradiz sua razão declarada para postar uma resposta. 3) Tem um link para um vídeo sem incluir as citações relevantes nesta resposta (o que pode não estar de acordo com o licenciamento, de qualquer maneira).
jpmc26
Você não pode confiar em coisas como current implementationse não for explicitamente garantido pela documentação. A implementação pode mudar a qualquer momento sem aviso prévio.
Victor Yarema
@ jpmc26, sim, não é uma substituição completa de List, é claro, no entanto, o mesmo é válido para ConcurrentBag como uma resposta aceita - não é uma substituição estrita de List, mas uma solução alternativa. Para responder às suas preocupações: 1) ConcurrentDictionary é um dicionário, não uma lista que você está certo, no entanto, a lista tem um array atrás, que pode indexar em O (1) o mesmo que o dicionário com int como uma chave 2) sim, a ordem não é garantida pelo doc ( embora esteja preservado), mas o ConcurrentBag aceito não pode garantir a ordem também em cenários multithread
tytyryty,
9

A ArrayListclasse C # tem um Synchronizedmétodo.

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

Isso retorna um wrapper thread-safe em torno de qualquer instância de IList. Todas as operações precisam ser realizadas através do wrapper para garantir a segurança do thread.

Hani Nakhli
fonte
1
De que idioma vocês estão falando?
John Demetriou
Java? Um dos poucos recursos de que sinto falta. Mas geralmente é escrito como: Collections.synchronizedList (new ArrayList ());
Nick
2
Isso é C # válido, supondo que você tenha um usando System.Collections ou poderia usar var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234
5

Se você olhar o código-fonte para List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), você perceberá que há uma classe lá (que é claro interno - por que, Microsoft, por quê?!?!) chamado SynchronizedList de T. Estou copiando colando o código aqui:

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

Pessoalmente, acho que eles sabiam que uma implementação melhor usando o SemaphoreSlim poderia ser criada, mas não chegaram a ela.

Siderite Zackwehdex
fonte
2
1 Bloquear toda a coleção ( _root) em cada acesso (leitura / gravação) torna esta uma solução lenta. Talvez seja melhor para essa classe permanecer interna.
Xaqron
3
Esta implementação não é segura para threads. Ele ainda exibe "System.InvalidOperationException: 'A coleção foi modificada; a operação de enumeração pode não ser executada.'"
Raman Zhylich
2
Isso não está relacionado à segurança do thread, mas ao fato de que você está iterando e alterando a coleção. A exceção é lançada pelo enumerador quando vê que a lista foi alterada. Para contornar isso, você precisa implementar seu próprio IEnumerator ou alterar o código para que ele não itere e altere a mesma coleção ao mesmo tempo.
Siderite Zackwehdex
Não é seguro para thread porque a coleção pode ser alterada durante os métodos "sincronizados". Isso absolutamente faz parte da segurança do segmento. Considere chamadas de um thread Clear()após outras chamadas, this[index]mas antes que o bloqueio seja ativado. indexnão é mais seguro de usar e lançará uma exceção quando finalmente for executado.
Suncat2000
2

Você também pode usar o mais primitivo

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

qual bloqueio usa (veja este post C # Bloqueando um objeto que é reatribuído no bloco de bloqueio ).

Se você está esperando exceções no código, isso não é seguro, mas permite que você faça algo como o seguinte:

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

Uma das coisas boas sobre isso é que você obterá o bloqueio durante a série de operações (em vez de bloquear em cada operação). O que significa que a saída deve vir nas partes certas (meu uso disso foi obter alguma saída na tela de um processo externo)

Eu realmente gosto da simplicidade + transparência do ThreadSafeList + que faz a parte importante para parar travamentos

JonnyRaa
fonte
2

No .NET Core (qualquer versão), você pode usar ImmutableList , que possui todas as funcionalidades do List<T>.

JotaBe
fonte
1

Eu acredito que _list.ToList()vou te fazer uma cópia. Você também pode consultá-lo se precisar, como:

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

De qualquer forma, o msdn diz que isso é de fato uma cópia e não simplesmente uma referência. Ah, e sim, você precisará bloquear o método definido como os outros apontaram.

Jonathan Henson
fonte
1

Parece que muitas das pessoas que estão achando isso estão querendo uma coleção de tamanho dinâmico indexada com thread safe. A coisa mais próxima e fácil que conheço é.

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

Isso exigiria que você garantisse que sua chave fosse devidamente incriminada, se você deseja um comportamento de indexação normal. Se você for cuidadoso, .count pode ser suficiente como a chave para quaisquer novos pares de valores-chave que você adicionar.

user2163234
fonte
1
Por que a chave deveria ser incriminada se não foi culpa dela?
Suncat2000
@ Suncat2000 ha!
Richard II
1

Eu sugeriria que qualquer pessoa que lida com List<T>cenários de multi-threading dê uma olhada em Immutable Collections, em particular o ImmutableArray .

Achei muito útil quando você tem:

  1. Relativamente poucos itens na lista
  2. Nem tantas operações de leitura / gravação
  3. MUITOS acessos simultâneos (ou seja, muitos tópicos que acessam a lista no modo de leitura)

Também pode ser útil quando você precisa implementar algum tipo de comportamento semelhante a uma transação (ou seja, reverter uma operação de inserção / atualização / exclusão em caso de falha)

adospace
fonte
-1

Aqui está a aula que você pediu:

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 );
                    }
                }
            }
        }
    }
}
Próximo
fonte
A versão no Google Drive é atualizada conforme eu atualizo a aula. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous
Por que this.GetEnumerator();quando @Tejs sugere this.Clone().GetEnumerator();?
Cœur
Porque [DataContract( IsReference = true )]?
Cœur
A última versão está agora no GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous de
Eu encontrei e consertei dois pequenos bugs nos métodos Add (). PARA SUA INFORMAÇÃO.
Protíguo
-3

Basicamente, se você deseja enumerar com segurança, você precisa usar o bloqueio.

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

Aqui está uma parte do MSDN que você pode estar interessado:

Membros públicos estáticos (compartilhados no Visual Basic) desse tipo são thread-safe. Não é garantido que nenhum membro da instância seja thread-safe.

Uma lista pode oferecer suporte a vários leitores simultaneamente, desde que a coleção não seja modificada. Enumerar por meio de uma coleção não é intrinsecamente um procedimento thread-safe. No caso raro em que uma enumeração contende com um ou mais acessos de gravação, a única maneira de garantir a segurança do thread é bloquear a coleção durante toda a enumeração. Para permitir que a coleção seja acessada por vários threads para leitura e gravação, você deve implementar sua própria sincronização.

istudy0
fonte
2
Não é verdade. Você pode usar conjuntos simultâneos.
ANeves
-3

Aqui está a classe para lista de thread segura sem bloqueio

 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
fonte
This is not threadsafe
Aldracor
_i ++ não é threadsafe. você deve usar um acréscimo atômico ao incrementá-lo e provavelmente também marcá-lo como volátil. CheckReset () não é threadsafe. Qualquer coisa pode acontecer entre a verificação condicional e a chamada para Reset (). Não escreva seus próprios utilitários de multithreading.
Chris Rollins
-15

Use a lockdeclaração para fazer isso. ( Leia aqui para obter mais informações. )

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 sua informação, isso provavelmente não é exatamente o que você está perguntando - você provavelmente deseja bloquear mais em seu código, mas não posso assumir isso. Dê uma olhada nolock palavra chave e adapte seu uso à sua situação específica.

Se você precisar, você pode lockno bloco gete setusando a _listvariável que faria com que uma leitura / gravação não pudesse ocorrer ao mesmo tempo.

Josh M.
fonte
1
Isso não vai resolver o problema dele; ele apenas impede que os threads definam a referência, não adicionando à lista.
Tejs
E se um thread estiver definindo o valor enquanto outro está iterando a coleção (é possível com seu código).
Xaqron
Como eu disse, o bloqueio provavelmente terá que ser movido mais adiante no código. Este é apenas um exemplo de como usar a instrução de bloqueio.
Josh M.
2
@Joel Mueller: Claro, se você fabrica algum exemplo bobo como esse. Estou apenas tentando ilustrar que o autor da pergunta deve examinar a lockdeclaração. Usando um exemplo semelhante, eu poderia argumentar que não devemos usar loops for, uma vez que você pode travar o aplicativo com quase nenhum esforço:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Josh M.
5
Nunca afirmei que seu código significava um impasse instantâneo. É uma resposta ruim para esta pergunta em particular pelos seguintes motivos: 1) Não protege contra o conteúdo da lista sendo modificado durante a enumeração da lista, ou por dois threads de uma vez. 2) Bloquear o setter, mas não o getter, significa que a propriedade não é realmente segura para thread. 3) Bloquear qualquer referência que seja acessível de fora da classe é amplamente considerado uma prática ruim, pois aumenta drasticamente as chances de deadlock acidental. É por isso lock (this)e lock (typeof(this))são grandes proibições.
Joel Mueller