С# 4.0: Существуют ли готовые, потокобезопасные свойства, реализуемые автоматически?

Я хотел бы иметь потокобезопасный доступ для чтения и записи к автоматически реализованному свойству. Мне не хватает этой функциональности из среды С#/.NET, даже в ней последняя версия. В лучшем случае я ожидал бы что-то вроде

[Threadsafe]
public int? MyProperty { get; set; }

Я знаю, что для этого есть различные примеры кода, но Я просто хотел быть уверенным, что это все еще невозможно, используя только методы .NET Framework, прежде чем внедрять что-то самостоятельно. Я не прав?

EDIT: поскольку некоторые ответы касаются атомарности, я хочу сказать, что я просто хочу иметь это, насколько я понимаю: до тех пор, пока (и не дольше) один поток читает значение свойства, никакому другому потоку не разрешено изменять значение. Таким образом, многопоточность не вводит недопустимые значения. Я выбрал int? потому что это то, о чем я сейчас беспокоюсь.

EDIT2: Я нашел конкретный ответ на пример с Nullable здесь, Эриком Липпертом

Ответ 1

Правильно; такого устройства нет. Предположительно, вы пытаетесь защитить от чтения поля, в то время как другой поток изменил его половину (атомарность)? Обратите внимание, что многие (малые) примитивы по своей сути безопасны от этого типа проблемы с потоками:

5.5 Атоматичность переменных ссылок

Чтение и запись следующих данных типы являются атомарными: bool, char, byte, sbyte, short, ushort, uint, int, float и ссылочных типов. В дополнение, чтение и запись перечисления типы с базовым типом в предыдущий список также является атомарным.

Но, честно говоря, это всего лишь кончик нитевидного льда; сам по себе его обычно недостаточно, чтобы иметь только потокобезопасное свойство; в большинстве случаев объем синхронизированного блока должен быть больше, чем просто одно чтение/запись.

Существует так много разных способов сделать что-то потокобезопасное, в зависимости от профиля доступа;

  • lock?
  • ReaderWriterLockSlim?
  • привязка ссылок к некоторому классу (по существу, a Box<T>, поэтому a Box<int?> в этом случае)
  • Interlocked (во всех обличьях)
  • volatile (в некоторых сценариях это не волшебная палочка...)
  • и т.д.

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

Ответ 2

Я отвечаю здесь, чтобы добавить к ответу Marc, где он говорит: "Существует так много разных способов сделать что-то потокобезопасное, в зависимости от профиля доступа".

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

С почти любым изменчивым объектом будут способы справиться с ним, которые не являются потокобезопасными (обратите внимание почти на все, возникает исключение). Рассмотрим потокобезопасную очередь, в которой есть следующие (потокобезопасные) элементы; операция очереди, операция детекции и свойство count. Относительно легко построить один из них либо через внутреннюю фиксацию на каждом элементе, либо даже без блокировки.

Однако, скажем, мы использовали объект следующим образом:

if(queue.Count != 0)
    return queue.Dequeue();

Вышеприведенный код не является потокобезопасным, потому что нет гарантии, что после (потокобезопасного) Count возврата 1 другой поток не будет деактивирован и, следовательно, приведет к сбою второй операции.

Он по-прежнему является потокобезопасным объектом по-разному, особенно в том случае, если даже в этом случае сбой, операция failing dequeue не приведет объект к недопустимому состоянию.

Чтобы сделать объект целым потокобезопасным перед лицом любой данной комбинации операций, мы должны либо сделать его логически неизменным (возможно наличие внутренней изменчивости с поточно-безопасными операциями, обновляющими внутреннее состояние в качестве оптимизации - например, через memoisation или загрузку из источника данных по мере необходимости, но снаружи он должен казаться неизменным) или значительно уменьшить количество возможных внешних операций (мы могли бы создать потокобезопасную очередь, которая имела только Enqueue и TryDequeue, которая всегда поточно-безопасный, но что оба уменьшают возможные операции, а также заставляют неудачный деинтез переопределяться как не сбой, а также приводят к изменению логики при вызове кода из предыдущей версии).

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

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

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

Также неясно, насколько широки или узки проблемы concurrency. Нужно ли блокировать (или подобное) только на этом свойстве, или нам нужно блокировать все свойства? Нужно ли нам также блокировать неавтоматические операции, и возможно ли это?

Здесь нет хорошего однозначного ответа (они могут быть сложными вопросами, чтобы ответить на то, чтобы опрокинуть ваше собственное решение, неважно, пытаясь ответить на него в коде, который будет генерировать такой код, когда кто-то использовал этот атрибут [Threadsafe]),.

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

Не имея возможности найти единый универсальный ответ на эти вопросы, нет хорошего способа обеспечить единую универсальную реализацию, и любой такой атрибут [Threadsafe] был бы в лучшем случае очень ограниченным. Наконец, на психологическом уровне использующего его программиста, это, скорее всего, приведет к ложному пониманию безопасности, что они создали поточно-безопасный класс, когда на самом деле они этого не сделали; что сделало бы его на самом деле хуже, чем бесполезно.

Ответ 3

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

Ответ 4

В соответствии с спецификацией С# 4.0 это поведение не изменяется:

Раздел 10.7.3 Автоматически реализованные свойства

Когда свойство указано как автоматически реализованное свойство, для свойства автоматически доступно скрытое поле поддержки, а аксессоры реализованы для чтения и записи в это фоновое поле.

Следующий пример:

 public class Point {
    public int X { get; set; } // automatically implemented
    public int Y { get; set; } // automatically implemented
}

эквивалентно следующему объявлению:

public class Point {
    private int x;
    private int y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}