Допустим, у вас есть простая операция, которая выполняется в фоновом потоке. Вы хотите предоставить способ отменить эту операцию, чтобы создать логический флаг, для которого вы установили значение true из обработчика события click кнопки отмены.
private bool _cancelled;
private void CancelButton_Click(Object sender ClickEventArgs e)
{
_cancelled = true;
}
Теперь вы устанавливаете флаг отмены из потока GUI, но читаете его из фонового потока. Вам нужно заблокировать доступ к bool?
Нужно ли вам это сделать (и, очевидно, также заблокировать обработчик события нажатия кнопки):
while(operationNotComplete)
{
// Do complex operation
lock(_lockObject)
{
if(_cancelled)
{
break;
}
}
}
Или это допустимо (без блокировки):
while(!_cancelled & operationNotComplete)
{
// Do complex operation
}
Или как пометить переменную _cancelled как volatile. Это необходимо?
[Я знаю, что есть класс BackgroundWorker с его встроенным методом CancelAsync (), но меня интересует семантика и использование блокировки и доступа к многопоточным переменным, а не конкретная реализация, код является лишь примером.]
Кажется, есть две теории.
1) Поскольку это простой встроенный тип (а доступ к встроенным типам является атомарным в .net) и поскольку мы пишем в него только в одном месте и читаем только в фоновом потоке, нет необходимости блокировать или отмечать как летучие.
2) Вы должны пометить его как volatile, потому что если вы этого не сделаете, компилятор может оптимизировать чтение в цикле while, потому что он ничего не думает, что он способен изменить значение.
Какая техника правильная? (А почему?)
[Редактировать: Кажется, есть две четко определенные и противоположные точки зрения по этому вопросу. Я ищу точный ответ на этот вопрос, поэтому, если возможно, опубликуйте свои причины и приведите источники вместе с ответом.]