Подтвердить что ты не робот

Использование операции 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)"

Как мне решить эту проблему?

4b9b3361

Ответ 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);
    }
}

Ответ 4

Вы можете использовать Interlocked.Exchange для int для этого:

int boolValue = 0;

// ...

if (System.Threading.Interlocked.Exchange(ref boolValue, 1) == 1)
{
    // Was True
}
else
{
    // Was False                
}