Использование операции Interlocked.CompareExchange() для значения bool?
У меня есть два вопроса:
-
Нужно ли использовать класс Interlocked для доступа к булевым значениям? Не является ли чтение или запись логическим значением атома по умолчанию?
-
Я попытался использовать Interlocked.CompareExchange на логическом и получил следующую ошибку:
bool value = true;
Interlocked.CompareExchange<bool>(ref value, false, true);
Ошибка: тип "bool" должен быть ссылочным типом, чтобы использовать его как параметр "T" в общем типе или методе "System.Threading.Interlocked.CompareExchange(ref T, T, T)"
Как мне решить эту проблему?
Ответы
Ответ 1
-
Чтение или запись логических значений отдельно , но "сравнение и обмен" выполняет как чтение, так и запись на один и тот же адрес, что означает, что целая транзакция не атомный. Если несколько потоков могут записываться в это же место, вам нужно сделать всю транзакцию атомной, используя класс Interlocked
.
-
public static T CompareExchange<T>(ref T a, T b, T c)) where T : class
перегрузка может использоваться только с ссылочными типами (обратите внимание на предложение where T : class
в конце). Вместо булевского значения вы можете использовать перегрузку CompareExchange(Int32, Int32, Int32)
и переключить логическое значение с помощью Int32
.
В качестве альтернативы, если вы хотите сохранить переменные типа boolean, вы можете использовать метод lock
для обеспечения безопасности потоков. Это будет немного более медленное решение, но в зависимости от ваших требований к производительности это может быть по-прежнему предпочтительным.
Ответ 2
Сбросьте свой собственный класс "AtomicBoolean" (который обертывает Interlocked.CompareExchange(...)
)
using System.Threading;
public class AtomicBoolean
{
private const int TRUE_VALUE = 1;
private const int FALSE_VALUE = 0;
private int zeroOrOne = FALSE_VALUE;
public AtomicBoolean()
: this(false)
{ }
public AtomicBoolean(bool initialValue)
{
this.Value = initialValue;
}
/// <summary>
/// Provides (non-thread-safe) access to the backing value
/// </summary>
public bool Value
{
get
{
return zeroOrOne == TRUE_VALUE;
}
set
{
zeroOrOne = (value ? TRUE_VALUE : FALSE_VALUE);
}
}
/// <summary>
/// Attempt changing the backing value from true to false.
/// </summary>
/// <returns>Whether the value was (atomically) changed from false to true.</returns>
public bool FalseToTrue()
{
return SetWhen(true, false);
}
/// <summary>
/// Attempt changing the backing value from false to true.
/// </summary>
/// <returns>Whether the value was (atomically) changed from true to false.</returns>
public bool TrueToFalse()
{
return SetWhen(false, true);
}
/// <summary>
/// Attempt changing from "whenValue" to "setToValue".
/// Fails if this.Value is not "whenValue".
/// </summary>
/// <param name="setToValue"></param>
/// <param name="whenValue"></param>
/// <returns></returns>
public bool SetWhen(bool setToValue, bool whenValue)
{
int comparand = whenValue ? TRUE_VALUE : FALSE_VALUE;
int result = Interlocked.CompareExchange(ref zeroOrOne, (setToValue ? TRUE_VALUE : FALSE_VALUE), comparand);
bool originalValue = result == TRUE_VALUE;
return originalValue == whenValue;
}
}
Пример использования:
class MultithreadedClass
{
private AtomicBoolean isUpdating = new AtomicBoolean(false);
public void Update()
{
if (!this.isUpdating.FalseToTrue())
{
return; //a different thread is already updating
}
try
{
//... do update.
}
finally
{
this.isUpdating.Value = false; //we are done updating
}
}
}
Тестирование (если вы собираетесь использовать его в процессе производства):
[TestClass]
public class AtomicBooleanTest
{
[TestMethod]
public void TestAtomicBoolean()
{
AtomicBoolean b = new AtomicBoolean();
Assert.IsFalse(b.Value);
b = new AtomicBoolean(false);
Assert.IsFalse(b.Value);
b = new AtomicBoolean(true);
Assert.IsTrue(b.Value);
//when Value is already true, FalseToTrue fails
b.Value = true;
Assert.IsFalse(b.FalseToTrue());
Assert.IsTrue(b.Value);
//when Value is already false, TrueToFalse fails
b.Value = false;
Assert.IsFalse(b.TrueToFalse());
Assert.IsFalse(b.Value);
//Value not changed if SetWhen fails
b.Value = false;
Assert.IsFalse(b.SetWhen(true, true));
Assert.IsFalse(b.Value);
//Value not changed if SetWhen fails
b.Value = true;
Assert.IsFalse(b.SetWhen(false, false));
Assert.IsTrue(b.Value);
}
}
Ответ 3
Вы не можете использовать блокировку для Boolean. Вместо этого вам лучше использовать int.
http://connect.microsoft.com/VisualStudio/feedback/details/98293/interlocked-compareexchange-should-offer-an-overload-for-boolean
Ответ 4
Вы можете использовать Interlocked.Exchange для int
для этого:
int boolValue = 0;
// ...
if (System.Threading.Interlocked.Exchange(ref boolValue, 1) == 1)
{
// Was True
}
else
{
// Was False
}