Им не * нужна ** волатильность, потому что вы никогда не проверяете значение блокированной переменной. Вместо этого вы всегда проверяете значение , возвращаемое с помощью операции (ов) с блокировкой. Смешанные взаимосвязанные операции и обычное присваивание / сравнение всегда приводят к неправильному коду.
Я не уверен, каково намерение функции Reset (), но этот фрагмент кода не помещается в примитив между потоками: вы присваиваете m_remain, вы проверяете значение m_remain напрямую, это довольно плохо. Я настоятельно рекомендую вам снять его: не только он реализован неправильно, но я очень сомневаюсь, что семантика «сброса» счетчика среднего срока службы необходима. Оставьте все просто: ctor (переместите код из Reset в него) Signal и Wait - это только три необходимых оператора, и они верны, как и сейчас.
Обновлено После того, как вы отредактировали код.
Игнорируя тот факт, что вы не должны смешивать два, если вы в конечном итоге смешаете их, тогда да, энергозависимость все еще необходима. Volatile в первую очередь относится к коду IL и коду JIT, сгенерированному для обеспечения того, чтобы значение всегда считывалось из фактической ячейки памяти и не происходила оптимизация, например, переупорядочение кода. Тот факт, что несвязанный фрагмент кода обновляет значение с помощью взаимосвязанных операций, не влияет на другие части, которые читают значение. Без атрибута volatile
компилятор / JIT может по-прежнему генерировать код, который игнорирует записи, которые происходят где-то еще, не имеет значения, если записи заблокированы или прямое назначение.
Кстати, существуют допустимые шаблоны, которые смешивают обычные операции чтения и блокированные операции, но обычно они включают Interlocked.CompareExchange и такие действия: чтение текущего состояния, выполнение некоторых вычислений на основе текущего состояния, попытка заменить состояние как сравнение с блокировкой -exchange: если все в порядке, если нет, отбросьте результат вычисления и вернитесь к шагу 1.