Ключевое слово volatile не следует путать с тем, чтобы сделать _a, _b и _c поточно-ориентированными. См. здесь для лучшего объяснения. Кроме того, ManualResetEvent не имеет никакого отношения к безопасности потоков _a, _b и _c. Вы должны управлять этим отдельно.
РЕДАКТИРОВАТЬ: с помощью этого редактирования я пытаюсь отфильтровать всю информацию, которая была включена в различные ответы и комментарии по этому вопросу.
Основной вопрос заключается в том, будут ли переменные результата (_a, _b и _c) «видимыми» в тот момент, когда переменная флага (_completed) вернет true.
Давайте на минутку предположим, что ни одна из переменных не помечена как изменчивая. В этом случае было бы возможно установить переменные результата после переменная флага установлена в Task (), например:
private void Task()
{
// possibly long-running work goes here
_completed = true;
_a = result1;
_b = result2;
_c = result3;
_completedSignal.Set();
}
Это явно не то, что мы хотим, так как нам с этим бороться?
Если эти переменные помечены как изменчивые, то это изменение порядка будет предотвращено. Но именно это побудило исходный вопрос - требуются ли летучие вещества или ManualResetEvent обеспечивает неявный барьер памяти, так что переупорядочение не происходит, и в этом случае ключевое слово volatile не является действительно необходимым?
Если я правильно понимаю, позиция wekempf заключается в том, что функция WaitOne () обеспечивает неявный барьер памяти, который решает проблему. НО , что мне кажется недостаточным. Основной и фоновый потоки могут выполняться на двух отдельных процессорах. Таким образом, если Set () также не обеспечивает неявный барьер памяти, то в конечном итоге функция Task () может быть выполнена на одном из процессоров (даже с переменными переменными):
private void Task()
{
// possibly long-running work goes here
_completedSignal.Set();
_a = result1;
_b = result2;
_c = result3;
_completed = true;
}
Я искал все выше и ниже информацию о барьерах памяти и EventWaitHandles, и ничего не нашел. Единственное упоминание, которое я видел, это то, что wekempf сделал для книги Джеффри Рихтера. Проблема с этим заключается в том, что EventWaitHandle предназначен для синхронизации потоков, а не доступа к данным. Я никогда не видел ни одного примера, где EventWaitHandle (например, ManualResetEvent) используется для синхронизации доступа к данным. Поэтому мне трудно поверить, что EventWaitHandle делает что-либо в отношении барьеров памяти. В противном случае я бы ожидал найти некоторую ссылку на это в Интернете.
РЕДАКТИРОВАТЬ # 2: Это ответ на ответ wekempf на мой ответ ...;)
Мне удалось прочитать раздел из книги Джеффри Рихтера на amazon.com. Со страницы 628 (wekempf цитирует это тоже):
Наконец, я должен отметить, что всякий раз, когда поток вызывает блокированный метод, процессор вызывает когерентность кэша. Поэтому, если вы манипулируете переменными с помощью взаимосвязанных методов, вам не нужно беспокоиться обо всех этих вещах модели памяти. Кроме того, все блокировки синхронизации потоков ( Monitor , ReaderWriterLock , Mutex , Semaphone , AutoResetEvent , ManualResetEvent и т. Д.) Внутренне вызывает взаимосвязанные методы.
Таким образом, может показаться, что, как указал wekempf, переменные результата не требуют ключевое слово volatile в примере, как показано, поскольку ManualResetEvent обеспечивает когерентность кэша.
Прежде чем закрыть это редактирование, я хотел бы сделать еще два замечания.
Во-первых, я исходил из того, что фоновый поток потенциально может запускаться несколько раз. Я очевидно пропустил название класса (OneUseBackgroundOp)! Учитывая, что он запускается только один раз, мне не ясно, почему функция DoSomething () вызывает WaitOne () так, как она это делает. Какой смысл ждать initialWaitMs миллисекунд, если фоновый поток может выполняться или не выполняться во время возврата DoSomething ()? Почему бы просто не запустить фоновый поток и использовать блокировку для синхронизации доступа к переменным результатов ИЛИ , просто выполнить содержимое функции Task () как часть потока, который вызывает DoSomething ( )? Есть ли причина не делать этого?
Во-вторых, мне кажется, что не использовать какой-то механизм блокировки переменных результатов по-прежнему плохой подход. Правда, это не нужно в коде, как показано. Но в какой-то момент в будущем может возникнуть другой поток и попытаться получить доступ к данным. Мне бы лучше подготовиться к этой возможности сейчас, чем пытаться выследить загадочные аномалии поведения позже.
Спасибо всем за поддержку. Я, безусловно, многому научился, участвуя в этой дискуссии.