Поточно-ориентированное свойство List <T>

123

Мне нужна реализация List<T>as свойства, которое можно без сомнения использовать поточно.

Что-то вроде этого:

private List<T> _list;

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

Кажется, мне все еще нужно вернуть копию (клонированную) коллекции, поэтому, если где-то мы выполняем итерацию коллекции, и в то же время коллекция установлена, то исключение не возникает.

Как реализовать потокобезопасное свойство коллекции?

Xaqron
источник
4
используйте замки, которые должны это делать.
atoMerz
Можно ли использовать поточно-ориентированную реализацию IList<T>(vs List<T>)?
Грег
2
Вы проверили SynchronizedCollection <T> ?
Saturn Technologies
Use BlockingCollection или ConcurrentDictionary
Кумар Чандракету
Какие операции вам нужно сделать с объектом, находящимся за свойством? Неужели вам не нужно все, что List<T>орудует? Если да, то не могли бы вы предоставить интерфейс, который вам нужен, вместо того, чтобы спрашивать обо всем, что List<T>уже есть?
Виктор Ярема

Ответы:

186

Если вы ориентируетесь на .Net 4, в пространстве имен System.Collections.Concurrent есть несколько вариантов.

Вы можете использовать ConcurrentBag<T>в этом случае вместоList<T>

Бала Р
источник
5
Подобно List <T> и в отличие от Dictionary, ConcurrentBag принимает дубликаты.
The Light
115
ConcurrentBag- это неупорядоченная коллекция, поэтому в отличие от List<T>нее не гарантируется порядок. Также вы не можете получить доступ к элементам по индексу.
Radek Stromský
11
@ RadekStromský прав, и в случае, если вам нужен упорядоченный параллельный список, вы можете попробовать ConcurrentQueue (FIFO) или ConcurrentStack (LIFO) .
Caio Cunha
7
Может быть, SynchronizedCollection <T> ?
Saturn Technologies
12
ConcurrentBag не реализует IList и на самом деле не является поточно-
ориентированной
87

Несмотря на то, что он получил наибольшее количество голосов, его обычно нельзя рассматривать System.Collections.Concurrent.ConcurrentBag<T>как поточно- System.Collections.Generic.List<T>ориентированную замену, поскольку она (Радек Стромски уже указал на это) не заказана.

Но есть класс с именем, System.Collections.Generic.SynchronizedCollection<T>который уже существует с .NET 3.0 как часть фреймворка, но он так хорошо спрятан в месте, где никто не ожидает, что он малоизвестен, и, вероятно, вы никогда не сталкивались с ним (по крайней мере, Я никогда не делал).

SynchronizedCollection<T>компилируется в сборку System.ServiceModel.dll (которая является частью клиентского профиля, но не переносимой библиотеки классов).

Надеюсь, это поможет.

Christoph
источник
3
Я плачу, что этого нет в основной библиотеке: {Часто бывает достаточно простой синхронизированной коллекции.
user2864740
Дополнительное полезное обсуждение этого варианта: stackoverflow.com/a/4655236/12484
Джон Шнайдер,
2
Он хорошо скрыт, потому что устарел, в пользу классов в System.Collections.Concurrent.
denfromufa
3
И недоступно в ядре .net
denfromufa
2
@denfromufa похоже, что они добавили это в .net core 2.0 docs.microsoft.com/en-gb/dotnet/api/…
Cirelli94
17

Я бы подумал, что создать образец класса ThreadSafeList будет легко:

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

Вы просто клонируете список перед тем, как запрашивать счетчик, и, таким образом, любое перечисление работает с копии, которую нельзя изменить во время работы.

Tejs
источник
1
Разве это не мелкий клон? Если Tэто ссылочный тип, не вернет ли он просто новый список, содержащий ссылки на все исходные объекты? Если это так, этот подход все равно может вызвать проблемы с потоками, поскольку к объектам списка могут обращаться несколько потоков через разные «копии» списка.
Joel B
3
Правильно, это неглубокая копия. Суть заключалась в том, чтобы просто иметь клонированный набор, который можно было бы безопасно перебирать (поэтому newListне нужно добавлять или удалять какие-либо элементы, которые сделали бы счетчик недействительным).
Tejs 03
7
Должен ли _lock быть статическим?
Майк Уорд
4
Еще одна мысль. Безопасна ли эта реализация для нескольких авторов? Если нет, возможно, его следует называть ReadSafeList.
Майк Уорд
5
@MikeWard - я не думаю, что так должно быть, все экземпляры будут заблокированы, когда любой экземпляр будет клонирован!
Джош М.
11

Даже принятый ответ - ConcurrentBag, я не думаю, что это реальная замена списка во всех случаях, поскольку в комментарии Радека к ответу говорится: «ConcurrentBag - это неупорядоченная коллекция, поэтому, в отличие от List, она не гарантирует упорядочение. Также вы не можете получить доступ к элементам по индексу. ».

Поэтому, если вы используете .NET 4.0 или выше, обходным решением может быть использование ConcurrentDictionary с целым числом TKey в качестве индекса массива и TValue в качестве значения массива. Это рекомендуемый способ замены list в курсе Pluralsight по параллельным коллекциям C # . ConcurrentDictionary решает обе проблемы, упомянутые выше: доступ к индексу и упорядочивание (мы не можем полагаться на упорядочение, поскольку это хэш-таблица под капотом, но текущая реализация .NET сохраняет порядок добавления элементов).

tytyryty
источник
1
пожалуйста, укажите причины для -1
тытырыты
Я не голосовал против, и для этого нет причин, ИМО. Вы правы, но концепция уже упоминается в некоторых ответах. Для меня суть в том, что в .NET 4.0 появилась новая потокобезопасная коллекция, о которой я не знал. Не уверен, что использовал Сумку или Коллекцию для данной ситуации. +1
Xaqron
2
У этого ответа несколько проблем: 1) ConcurrentDictionaryэто словарь, а не список. 2) Сохранение порядка не гарантируется, как указано в вашем собственном ответе, что противоречит указанной вами причине публикации ответа. 3) Он ссылается на видео без соответствующих цитат в этом ответе (что в любом случае может не соответствовать их лицензированию).
jpmc26
Вы не можете полагаться на такие вещи, как current implementationесли это явно не гарантируется документацией. Реализация может быть изменена в любое время без предварительного уведомления.
Виктор Ярема
@ jpmc26, да, конечно, это не полная замена List, но то же самое и с ConcurrentBag в качестве принятого ответа - это не строгая замена List, но обходной путь. Чтобы ответить на ваши вопросы: 1) ConcurrentDictionary - это словарь, а не список, в котором вы правы, однако список имеет массив позади, который может индексироваться в O (1) так же, как словарь с int в качестве ключа 2) да порядок не гарантируется doc ( хотя он сохраняется), но принятый ConcurrentBag не может гарантировать порядок также и в многопоточных сценариях
тытырытый
9

У ArrayListкласса C # есть Synchronizedметод.

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

Это возвращает потокобезопасную оболочку вокруг любого экземпляра IList. Все операции необходимо выполнять через обертку, чтобы обеспечить безопасность потока.

Хани Нахли
источник
1
На каком языке ты говоришь?
Джон Деметриу
Ява? Одна из немногих особенностей, которые мне не хватает. Но обычно он записывается как: Collections.synchronizedList (new ArrayList ());
Ник
2
Это допустимый C # при условии, что вы используете System.Collections или можете использовать var System.Collections.ArrayList.Synchronized (new System.Collections.ArrayList ());
user2163234 01
5

Если вы посмотрите исходный код для List of T ( https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 ), вы заметите, что там есть класс (который, конечно, внутренний - почему, Microsoft, почему?!?!) называется SynchronizedList of T. Я копирую вставку кода здесь:

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

Лично я думаю, что они знали, что можно создать лучшую реализацию с использованием SemaphoreSlim , но не дошли до нее.

Сидерит Закведекс
источник
2
+1 Блокировка всей коллекции ( _root) при каждом доступе (чтение / запись) делает это решение медленным. Может быть, лучше оставить этот класс внутренним.
Xaqron
3
Эта реализация не является потокобезопасной. Он по-прежнему выдает «System.InvalidOperationException: 'Коллекция была изменена; операция перечисления может не выполняться.'»
Раман Жилич
2
Это не связано с безопасностью потоков, а связано с тем, что вы повторяете и изменяете коллекцию. Перечислитель выдает исключение, когда видит, что список был изменен. Чтобы обойти это, вам нужно реализовать свой собственный IEnumerator или изменить код, чтобы он не повторял и не изменял одну и ту же коллекцию одновременно.
Siderite Zackwehdex
Это небезопасно для потоков, потому что коллекция может быть изменена во время «синхронизированных» методов. Это абсолютно является частью безопасности потока. Рассматривайте вызовы одного потока Clear()после вызовов другого, this[index]но до активации блокировки. indexболее небезопасен в использовании и вызовет исключение при окончательном выполнении.
Suncat2000
2

Вы также можете использовать более примитивный

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

какую блокировку использует (см. этот пост C # Блокировка объекта, который переназначен в блоке блокировки ).

Если вы ожидаете исключения в коде, это небезопасно, но позволяет делать что-то вроде следующего:

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

Одна из приятных особенностей этого заключается в том, что вы получите блокировку на время серии операций (а не блокировку в каждой операции). Это означает, что вывод должен выводиться правильными фрагментами (я использовал это для вывода на экран какого-либо внешнего процесса)

Мне очень нравится простота + прозрачность ThreadSafeList +, который играет важную роль в предотвращении сбоев.

JonnyRaa
источник
2

В .NET Core (любой версии) вы можете использовать ImmutableList , который имеет все функции List<T>.

JotaBe
источник
1

Я верю _list.ToList()сделаю вам копию. Вы также можете запросить его, если вам нужно, например:

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

В любом случае, msdn говорит, что это действительно копия, а не просто ссылка. О, и да, вам нужно будет заблокировать метод set, как указывали другие.

Джонатан Хенсон
источник
1

Похоже, что многие люди, обнаружившие это, хотят иметь поточно-безопасную индексированную коллекцию динамического размера. Самым близким и простым, что я знаю, было бы.

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

Для этого потребуется убедиться, что ваш ключ правильно инкриминирован, если вы хотите нормального поведения индексации. Если вы будете осторожны .count может быть достаточно в качестве ключа для любых новых пар ключ-значение, которые вы добавляете.

user2163234
источник
1
Зачем обвинять ключ, если он не виноват?
Suncat2000
@ Suncat2000 га!
Ричард II
1

Я бы посоветовал всем, кто имеет дело с List<T>многопоточными сценариями, взглянуть на Immutable Collections, в частности на ImmutableArray .

Я нахожу это очень полезным, когда у вас есть:

  1. Относительно мало пунктов в списке
  2. Не так много операций чтения / записи
  3. МНОГО одновременного доступа (т.е. много потоков, которые обращаются к списку в режиме чтения)

Также может быть полезно, когда вам нужно реализовать какое-то поведение, подобное транзакции (например, отменить операцию вставки / обновления / удаления в случае сбоя)

adospace
источник
-1

Вот класс, о котором вы просили:

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 );
                    }
                }
            }
        }
    }
}
Protiguous
источник
Версия на Google Диске обновляется по мере обновления класса. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Protiguous
Почему, this.GetEnumerator();когда предлагает @Tejs this.Clone().GetEnumerator();?
Cœur
Почему [DataContract( IsReference = true )]?
Cœur
Последняя версия теперь на GitHub! github.com/AIBrain/Librainian/blob/master/Collections/…
Protiguous
Я обнаружил и исправил две небольшие ошибки в методах Add (). FYI.
Protiguous
-3

В принципе, если вы хотите безопасно перечислить, вам нужно использовать lock.

Пожалуйста, обратитесь к MSDN по этому поводу. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Вот часть MSDN, которая может вас заинтересовать:

Открытые статические (общие в Visual Basic) члены этого типа являются потокобезопасными. Потокобезопасность любых членов экземпляра не гарантируется.

Список может поддерживать несколько читателей одновременно, пока коллекция не изменяется. По сути, перечисление через коллекцию не является поточно-ориентированной процедурой. В редких случаях, когда перечисление соперничает с одним или несколькими доступами на запись, единственный способ обеспечить безопасность потоков - это заблокировать коллекцию на протяжении всего перечисления. Чтобы разрешить доступ к коллекции нескольким потокам для чтения и записи, вы должны реализовать свою собственную синхронизацию.

istudy0
источник
2
Совсем неправда. Вы можете использовать одновременные наборы.
ANeves
-3

Вот класс для безопасного списка потоков без блокировки

 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;
        }
    }
Сурадж Мангал
источник
Это небезопасно
Aldracor
_i ++ не является потокобезопасным. вам нужно использовать атомарное добавление, когда вы увеличиваете его, и, вероятно, также помечаете его как изменчивый. CheckReset () не является потокобезопасным. Между условной проверкой и вызовом Reset () может произойти что угодно. Не пишите свои собственные многопоточные утилиты.
Крис Роллинз,
-15

Используйте для этого lockинструкцию. ( Подробнее читайте здесь. )

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

К вашему сведению, это, вероятно, не совсем то, о чем вы спрашиваете - вы, вероятно, захотите заблокировать больше в своем коде, но я не могу этого предположить. Взгляните на lockключевое слово и адаптируйте его использование к вашей конкретной ситуации.

Если вам нужно, вы можете lockкак в блоке, так getи в setблоке, используя _listпеременную, которая сделает так, чтобы чтение / запись не происходило одновременно.

Джош М.
источник
1
Это не решит его проблемы; он только останавливает потоки от установки ссылки, но не добавляет в список.
Tejs
А что, если один поток устанавливает значение, а другой выполняет итерацию коллекции (это возможно с вашим кодом).
Xaqron
Как я уже сказал, замок, вероятно, придется переместить дальше в коде. Это просто пример того, как использовать оператор блокировки.
Джош М.
2
@Joel Mueller: Конечно, если вы создадите какой-нибудь такой глупый пример. Я просто пытаюсь проиллюстрировать, что спрашивающий должен изучить lockутверждение. Используя аналогичный пример, я мог бы утверждать, что нам не следует использовать циклы for, поскольку вы можете заблокировать приложение без каких-либо усилий:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Джош М.
5
Я никогда не утверждал, что ваш код означает мгновенный тупик. Это плохой ответ на этот конкретный вопрос по следующим причинам: 1) Он не защищает от изменения содержимого списка во время перечисления списка или сразу двумя потоками. 2) Блокировка установщика, но не получателя, означает, что свойство не является потокобезопасным. 3) Блокировка любой ссылки, доступной извне класса, широко считается плохой практикой, поскольку она резко увеличивает вероятность случайной блокировки. Вот почему lock (this)и lock (typeof(this))являются большими «нет».
Джоэл Мюллер