Потокобезопасный доступ к статической коллекции

#c# #collections #thread-safety

#c# #Коллекции #потокобезопасность

Вопрос:

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

 private static Dictionary<Type, List<PropertyInfo>> PropertyCache { get; set; }
  

Является ли это правильным способом реализации потокобезопасного подхода? По какой-то причине меня что-то беспокоит по поводу влияния static на Dictionary в этом случае. Если это правильно, есть ли неправильный способ, который можно продемонстрировать, чтобы я мог избежать этого в остальной части моего кода.

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

Если это имеет значение, свойство всегда будет объявлено private .

Заранее спасибо

Несмотря на то, что C # помечен тегом, ответы в VB в порядке, я, так сказать, «двуязычный»

Редактировать:

После обсуждения с Джоном Скитом в комментариях к его ответу использование будет:

 // I will do this
PropertyCache.Add(typeof(string), new List<PropertyInfo>());
PropertyCache.Remove(typeof(string));

// I will never do this 
PropertyCache = new Dictionary<Type, List<PropertyInfo>>();
  

Я также никогда не буду перебирать коллекцию, только доступ по ключу.

Комментарии:

1. Что означает «я собираюсь только добавлять — удалять элементы из коллекции»?

2. @SteveDanner, например, я никогда не собираюсь использовать PropertyCache = new Dictionary<Type, List<PropertyInfo>> только PropertyCache.Add(item) 🙂

3. Хорошо, тогда ReadOnlyDictionary работать не будет. Рассмотрим ответ Джона Скита, IMO.

4. Вы спрашиваете: «Является ли это правильным способом реализации потокобезопасного подхода?» Ответом на это будет решительное «Нет». Пометка чего-либо статического не имеет ничего общего с потокобезопасностью. Он просто помечает объект как общий для экземпляров объекта. Общий. Все экземпляры имеют доступ к одному и тому же объекту. Сам объект (или код, пытающийся получить к нему доступ) должен будет реализовать потокобезопасную логику при доступе.

Ответ №1:

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

Если вы используете .NET 4, вам следует рассмотреть возможность использования ConcurrentDictionary — в противном случае я бы, вероятно, написал средства доступа, которые просто получают блокировку для каждого доступа. Хотя вы могли бы использовать ReaderWriterLockSlim etc, я бы лично выбрал самый простой безопасный подход для начала.

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

Комментарии:

1. Спасибо, Джон. Пожалуйста, не могли бы вы подробнее рассказать о том, что вы подразумеваете под «простейшим безопасным подходом», чтобы у меня появилась идея о том, как я мог бы реализовать это и в приложении Net 35.

2. @SBlackler: Самый простой способ — просто заблокировать каждый доступ. Отредактирую с примером.

3. @SBlackler: На самом деле, я только что заметил, что значение представляет собой список … который также не будет потокобезопасным. Как вы собираетесь это использовать? После заполнения список будет доступен только для чтения?

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

5. @Jon Хороший момент — коллекция содержит коллекции. Существует множество возможностей для столкновения.

Ответ №2:

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

Коллекции, которые реализуют ICollection интерфейс, могут быть приведены для доступа к объекту, который SyncRoot вы можете использовать для lock объекта, чтобы гарантировать, что только один поток одновременно может выполнять серию инструкций, которые приведут к изменению коллекции. Использование SyncRoot для многопоточных приложений

 lock(((ICollection)myObject).SyncRoot)
{
    //Code that should be executed by only one concurrent thread
    //This is add/insert/remove/iterate/clear/etc.
}
  

В дополнение к этому руководству (или более старому школьному способу) в .NET 4 доступны параллельные объекты, которые выполняют в основном это, но с некоторыми другими специальными проверками. По большей части эти объекты настолько оптимизированы по производительности, насколько это возможно для полностью безопасных объектов. Если вы используете очень контролируемый и небольшой набор действий над объектом (у вас есть 1 метод добавления, 1 метод удаления и вы никогда не перечисляете всю коллекцию, а просто получаете доступ к определенным известным записям), вы могли бы использовать более легкий lock() пример выше, чтобы получить лучшую производительность.

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

Ответ №3:

То, что вы показали, никоим образом не является потокобезопасным. Если несколько потоков могут вставлять или удалять элементы одновременно, у вас потенциальная проблема.