Властивість списку <T>


122

Я хочу реалізувати List<T>як властивість, яку можна безпечно використовувати для потоків без будь-яких сумнівів.

Щось на зразок цього:

private List<T> _list;

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

Здається, мені все-таки потрібно повернути копію (клоновану) колекцію, тому якщо десь ми повторюємо колекцію і одночасно колекцію встановлюємо, тоді жодного винятку не виникає.

Як реалізувати безпечну для колекціонування властивість?


4
використовувати замки, які повинні це зробити.
atoMerz

Чи можна використовувати безпечну реалізацію IList<T>(vs List<T>)?
Грег

2
Ви перевірили SynchronizedCollection <T> ?
Saturn Technologies

Використовуйте BlockingCollection або ConcurrentDictionary
kumar chandraketu

Які операції потрібно виконати з об’єктом, що стоїть за властивістю? Можливо, вам не потрібно все, що List<T>реалізується? Якщо так, то чи можете ви надати інтерфейс, який вам потрібен, а не питати про все, що List<T>вже є?
Віктор Ярема

Відповіді:


185

Якщо ви орієнтуєтеся .Net 4 Є кілька варіантів в System.Collections.Concurrent просторі імен

Ви можете використовувати ConcurrentBag<T>в цьому випадку замістьList<T>


5
Як і список <T>, і на відміну від словника, ConcurrentBag приймає дублікати.
Світло

115
ConcurrentBagце невпорядкований збір, тому на відміну від List<T>нього не гарантує замовлення. Також ви не можете отримати доступ до елементів за індексом.
Радек Стромський

11
@ RadekStromský має рацію, і якщо ви хочете замовити паралельний список, ви можете спробувати ConcurrentQueue (FIFO) або ConcurrentStack (LIFO) .
Кайо Кунья


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 (яка є частиною профілю клієнта, але не бібліотеки портативного класу).

Сподіваюся, що це допомагає.


3
Я плачу, що це не в основній lib: {Проста синхронізована колекція - це часто все, що потрібно.
користувач2864740

Додаткова корисна дискусія щодо цього варіанту: stackoverflow.com/a/4655236/12484
Джон Шнайдер

2
Він добре прихований, тому що застарілий, на користь класів у System.Collections.Concurrent.
denfromufa

3
І недоступний у .net core
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;
    }
}

Ви просто клонуєте список перед тим, як подати запит на нумератор, і таким чином будь-яке перерахування опрацьовує копію, яку неможливо змінити під час запуску.


1
Хіба це не дрібний клон? Якщо Tтип посилання - це не повернеться новий список, що містить посилання на всі вихідні об'єкти? Якщо це так, такий підхід все ще може спричинити проблеми з ниткою, оскільки до об'єктів списку можна отримати доступ через декілька потоків через різні "копії" списку.
Джоель Б

3
Правильно, це неглибока копія. Сенс полягав у тому, щоб просто мати клонований набір, який би був безпечним для повторення (тому newListне було додано чи вилучено жодних предметів, які б визначили недійсним нумератор).
Tejs

7
Чи повинен _lock статичний?
Майк Уорд

4
Ще одна думка. Це реалізація потоку безпеки для декількох авторів? Якщо ні, можливо, це слід назвати ReadSafeList.
Майк Уорд

5
@MikeWard - Я не думаю, що так має бути, всі екземпляри будуть заблоковані, коли будь-який екземпляр клонується!
Джош М.

11

Навіть прийнята відповідь - ConcurrentBag, я не думаю, що це реальна заміна списку у всіх випадках, оскільки коментар Радека до відповіді говорить: "ConcurrentBag - це не упорядкована колекція, тому на відміну від" Список "вона не гарантує замовлення. Також ви не можете отримати доступ до елементів за індексом ".

Отже, якщо ви використовуєте .NET 4.0 або новішої версії, вирішенням може бути використання ConcurrentDictionary з цілим TKey як індекс масиву та TValue як значення масиву. Це рекомендований спосіб заміни списку в курсі одночасних колекцій C # Pluralsight . ConcurrentDictionary вирішує обидві проблеми, згадані вище: доступ до індексу та впорядкування (ми не можемо розраховувати на замовлення, оскільки це хеш-таблиця під кришкою, але поточна реалізація .NET зберігає порядок додавання елементів).


1
Будь ласка, вкажіть причини для -1
титирити

Я не проголосував, і ІМО немає підстав для цього. Ви праві, але концепція вже згадується в деяких відповідях. Для мене справа в тому, що у .NET 4.0 з'явилася нова безпечна для потоків колекція, про яку я не знав. Не впевнений, чи використовували мішок або колекцію для даної ситуації. +1
Xaqron

2
Ця відповідь має кілька проблем: 1) ConcurrentDictionary- це словник, а не список. 2) Замовлення зберігача не гарантується, як зазначено у вашій власній відповіді, що суперечить вашій заявленій причині опублікування відповіді. 3) Він посилається на відео, не вводячи відповідні цитати у цю відповідь (що це все одно не може відповідати їх ліцензуванню).
jpmc26

Ви не можете покладатися на такі речі, як, current implementationякщо це прямо не гарантується документацією. Реалізація може змінитися будь-коли без попереднього повідомлення.
Віктор Ярема

@ jpmc26, так, це не повна заміна для списку звичайно, однак те ж саме з ConcurrentBag як прийнята відповідь - це не сувора заміна списку, а обхід. Щоб відповісти на ваші занепокоєння: 1) ConcurrentDictionary - це словник, який ви не маєте у списку, проте у списку є масив, який може індексувати O (1) так само, як словник з int як ключ 2) так doc не гарантує порядок ( незважаючи на те, що він збережений), але прийнятий ConcurrentBag не може гарантувати порядок також у багатопотокових сценаріях
tytyryty

9

ArrayListКлас C # має Synchronizedметод.

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

Це повертає безпечну для обгортання потоку навколо будь-якого примірника IList. Всі операції потрібно виконувати через обгортку, щоб забезпечити безпеку нитки.


1
Якою мовою ви говорите?
Джон Деметріу

Java? Одна з небагатьох особливостей, які я про це сумую. Але зазвичай це записується як: Collections.synchronizedList (новий ArrayList ());
Нік

2
Це дійсно C # за умови, що ви використовуєте System.Collections або ви можете використовувати var System.Collections.ArrayList.Synchronized (новий System.Collections.ArrayList ());
користувач2163234

5

Якщо ви подивитеся на вихідний код для списку 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 +, що робить важливий біт у зупинці збоїв


2

У .NET Core (будь-яка версія) ви можете використовувати ImmutableList , який має всі функціональні можливості List<T>.


1

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

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

У будь-якому випадку, msdn каже, що це дійсно копія, а не просто посилання. О, і так, вам потрібно буде заблокувати встановлений метод, як вказували інші.


1

Схоже, що багато людей, які виявили це, хочуть колекцію безпечної нитки, індексовану динамічним розміром. Найближчим і найпростішим, що я знаю, було б.

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

Це вимагає, щоб переконатися, що ваш ключ належним чином інкримінується, якщо ви хочете нормальної поведінки щодо індексації. Якщо ви обережні. .Count може бути достатнім як ключ для будь-яких нових пар ключових значень, які ви додали.


1
Чому ключ повинен інкримінуватися, якщо він не був виною ключа?
Suncat2000

@ Suncat2000 га!
Річард II

1

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

Я вважаю це дуже корисним, коли у вас є:

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

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


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

Коли я оновлюю клас, версія на Диску Google оновлюється. uberscraper.blogspot.com/2012/12/c-thread-safe-list.html
Безперервний

Чому this.GetEnumerator();коли @Tejs пропонує this.Clone().GetEnumerator();?
Cœur

Чому [DataContract( IsReference = true )]?
Cœur


Я знайшов та виправив дві маленькі помилки методами Add (). FYI.
Безперервний

-3

В основному, якщо ви хочете перерахувати безпечно, вам потрібно застосувати замок.

Про це зверніться до MSDN. http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

Ось частина MSDN, яка може вас зацікавити:

Публічні статичні (Спільно доступні у Visual Basic) члени цього типу є безпечними для потоків. Будь-які члени екземплярів не гарантують безпеку потоку.

Список може одночасно підтримувати декілька читачів, доки колекція не буде змінена. Перерахування через колекцію по суті не є безпечною для потоків процедурою. У рідкісному випадку, коли перерахування суперечить одному або декільком доступам до запису, єдиний спосіб забезпечити безпеку ниток - заблокувати колекцію протягом усього перерахування. Щоб дозволити доступ до колекції декількома потоками для читання та запису, необхідно здійснити власну синхронізацію.


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


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

FYI, це, мабуть, не саме те, що ви просите - ви, ймовірно, хочете зафіксувати далі у своєму коді, але я не можу цього припустити. Погляньте наlock ключове слово та пристосуйте його використання до вашої конкретної ситуації.

Якщо вам потрібно, ви можете lockі в блоці, getі в setблоці, використовуючи _listзмінну, яка б зробила так, що читання / запис не може відбуватися одночасно.


1
Це не вирішить його проблеми; це лише зупиняє потоки від встановлення посилання, не додаючи до списку.
Tejs

А що робити, якщо один потік задає значення, а інший повторює колекцію (можливо, це стосується вашого коду).
Xaqron

Як я вже сказав, замок, ймовірно, доведеться перенести далі в код. Це лише приклад того, як використовувати оператор блокування.
Джош М.

2
@ Джоел Мюллер: Звичайно, якщо ви виготовите такий нерозумний приклад, як. Я просто намагаюся проілюструвати, що запитувач повинен вивчити lockтвердження. Використовуючи подібний приклад, я можу стверджувати, що ми не мусимо використовувати для циклів, оскільки ви можете навряд чи зафіксувати додаток:for (int x = 0; x >=0; x += 0) { /* Infinite loop, oops! */ }
Джош М.

5
Я ніколи не стверджував, що ваш код означав миттєвий тупик. Це неправильна відповідь на це конкретне запитання з наступних причин: 1) Це не захищає від змісту списку, що змінюється під час перерахування списку, або двома потоками одразу. 2) Блокування сетера, але не getter, означає, що властивість насправді не є безпечною для потоків. 3) Блокування будь-яких посилань, доступних за межами класу, вважається поганою практикою, оскільки різко збільшує шанси на випадковий тупик. Ось чому lock (this)і lock (typeof(this))великі ні-ні.
Джоель Мюллер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.