#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:
То, что вы показали, никоим образом не является потокобезопасным. Если несколько потоков могут вставлять или удалять элементы одновременно, у вас потенциальная проблема.