Потокобезопасный список <T>свойство - PullRequest
99 голосов
/ 03 мая 2011

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

Примерно так:

private List<T> _list;

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

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

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

Ответы [ 13 ]

164 голосов
/ 03 мая 2011

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

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

80 голосов
/ 03 мая 2013

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

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

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

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

16 голосов
/ 03 мая 2011

Я думаю, что создание примера класса 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;
    }
}

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

5 голосов
/ 31 марта 2017

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

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

4 голосов
/ 08 октября 2017

Вы можете использовать:

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

, чтобы создать безопасный поток ArrayLsit

3 голосов
/ 12 августа 2018

Если вы посмотрите на исходный код для List 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 голосов
/ 22 августа 2012

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

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 +, который играет важную роль в предотвращении сбоев

1 голос
/ 01 октября 2018

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

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

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

1 голос
/ 03 мая 2011

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

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

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

0 голосов
/ 26 июня 2019

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

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

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

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

...