C # 4.0: есть ли готовые, поточно-ориентированные, автоматически реализованные свойства? - PullRequest
11 голосов
/ 29 ноября 2010

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

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

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

РЕДАКТИРОВАТЬ: Поскольку некоторые ответы уточняют атомарность, я хочу заявить, что я просто хочу иметь это, насколько я понимаю: до тех пор, пока (и не более) один поток читает значение свойства никакой другой поток не может изменить значение. Таким образом, многопоточность не будет вводить недопустимые значения. Я выбрал инт? введите, потому что это то, что я в настоящее время беспокоюсь.

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

Ответы [ 4 ]

16 голосов
/ 29 ноября 2010

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

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

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

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

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

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

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

7 голосов
/ 29 ноября 2010

Я отвечаю здесь, чтобы добавить к ответу Марка, где он говорит, что «есть также очень много разных способов сделать что-то поточно-ориентированное, в зависимости от профиля доступа».

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1 голос
/ 29 ноября 2010

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

0 голосов
/ 29 ноября 2010

Согласно спецификации C # 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; } }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...