带枚举的Interlocked.CompareExchange

时间:2013-08-21 13:17:11

标签: c# interlocked

我正在尝试将Interlocked.CompareExchange用于此枚举:

public enum State {
    Idle,
    Running,
    //...
}

以下代码无法编译,但这就是我想要做的事情:

if (Interlocked.CompareExchange(ref state, State.Running, State.Idle) != State.Idle) {
    throw new InvalidOperationException("Unable to run - not idle");
}

当然我可以使用int而不是enum并使用属性:

private int state = (int)State.Idle;
public State { get { return (State)state; } }

然后将枚举转换为int:

if (Interlocked.CompareExchange(ref state, (int)State.Running, (int)State.Idle) !=  (int)State.Idle) {
    throw new InvalidOperationException("Unable to run - not idle");
}

但有更好的方法吗?

5 个答案:

答案 0 :(得分:17)

可以从IL中获取,并且可以为此创建一个可以在C#中使用的辅助方法。

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

static class CompareExchangeEnumImpl<T>
{
    public delegate T dImpl(ref T location, T value, T comparand);
    public static readonly dImpl Impl = CreateCompareExchangeImpl();

    static dImpl CreateCompareExchangeImpl()
    {
        var underlyingType = Enum.GetUnderlyingType(typeof(T));
        var dynamicMethod = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(T).MakeByRefType(), typeof(T), typeof(T) });
        var ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Ldarg_1);
        ilGenerator.Emit(OpCodes.Ldarg_2);
        ilGenerator.Emit(
            OpCodes.Call,
            typeof(Interlocked).GetMethod(
                "CompareExchange",
                BindingFlags.Static | BindingFlags.Public,
                null,
                new[] { underlyingType.MakeByRefType(), underlyingType, underlyingType },
                null));
        ilGenerator.Emit(OpCodes.Ret);
        return (dImpl)dynamicMethod.CreateDelegate(typeof(dImpl));
    }
}

public static class InterlockedEx
{
    public static T CompareExchangeEnum<T>(ref T location, T value, T comparand)
    {
        return CompareExchangeEnumImpl<T>.Impl(ref location, value, comparand);
    }
}

public enum Foo
{
    X,
    Y,
}

static class Program
{
    static void Main()
    {
        Foo x = Foo.X;
        Foo y = Foo.Y;
        y = InterlockedEx.CompareExchangeEnum(ref x, y, Foo.X);
        Console.WriteLine("x: " + x);
        Console.WriteLine("y: " + y);
    }
}

输出:

x: Y
y: X

这只是将参数转发给正确的Interlocked.Exchange重载。如果T实际上不是枚举类型,或者其基础类型没有Interlocked.Exchange重载,则会严重失败。

生成的IL是可验证的,至少根据PEVerify,可以通过使用AssemblyBuilder并将结果保存到文件来检查。

答案 1 :(得分:12)

为简单起见,不要: - )

可悲的是,C#/ .NET将enum视为完整类型,与其基本类型部分断开连接。每当你试图在enum上做一些“幻想”时,你会遇到一些障碍。

答案 2 :(得分:4)

  

但有更好的方法吗?

我使用的是一个类而不是枚举:

public class DataCollectionManagerState
{
    public static readonly DataCollectionManagerState Off = new DataCollectionManagerState() { };
    public static readonly DataCollectionManagerState Starting = new DataCollectionManagerState() { };
    public static readonly DataCollectionManagerState On = new DataCollectionManagerState() { };

    private DataCollectionManagerState() { }

    public override string ToString()
    {
        if (this == Off) return "Off";
        if (this == Starting) return "Starting";
        if (this == On) return "On";

        throw new Exception();
    }
}

public class DataCollectionManager
{
    private static DataCollectionManagerState _state = DataCollectionManagerState.Off;

    public static void StartDataCollectionManager()
    {
        var originalValue = Interlocked.CompareExchange(ref _state, DataCollectionManagerState.Starting, DataCollectionManagerState.Off);
        if (originalValue != DataCollectionManagerState.Off)
        {
            throw new InvalidOperationException(string.Format("StartDataCollectionManager can be called when it's state is Off only. Current state is \"{0}\".", originalValue.ToString()));
        }

        // Start Data Collection Manager ...

        originalValue = Interlocked.CompareExchange(ref _state, DataCollectionManagerState.On, DataCollectionManagerState.Starting);
        if (originalValue != DataCollectionManagerState.Starting)
        {
            // Your code is really messy
            throw new Exception(string.Format("Unexpected error occurred. Current state is \"{0}\".", originalValue.ToString()));
        }
    }
}

答案 3 :(得分:4)

枚举

Interlocked操作没有问题:

public enum State { Idle, Running }

unsafe State CompareExchange(ref State target, State v, State cmp)
{
    fixed (State* p = &target)
        return (State)Interlocked.CompareExchange(ref *(int*)p, (int)v, (int)cmp);
}

JSFiddle

上查看我的完整答案和讨论

答案 4 :(得分:1)

使用System.Runtime.CompilerServices.Unsafe

Here's一个很好的相关答案正在深入。

using System;
using System.Runtime.CompilerServices;
using System.Threading;

public static class InterlockedEx
{
    /// <summary>
    /// Enum equivalent of <see cref="Interlocked.CompareExchange(ref Int32, Int32, Int32)"/> and <see cref="Interlocked.CompareExchange(ref Int64, Int64, Int64)"/>
    /// </summary>
    public static TEnum CompareExchange<TEnum>(ref TEnum location, TEnum value, TEnum comparand)
        where TEnum : Enum
    {
        return Unsafe.SizeOf<TEnum>() switch
        {
            // .NET does not support 1- and 2-byte atomic operations as there
            // is no common hardware support for that.
            4 => CompareExchange32Bit(ref location, value, comparand),
            8 => CompareExchange64Bit(ref location, value, comparand),
            _ => throw new NotSupportedException("Only enums with an underlying type of 4 bytes or 8 bytes are allowed to be used with Interlocked")
        };

        static TEnum CompareExchange32Bit(ref TEnum location, TEnum value, TEnum comparand)
        {
            int comparandRaw = Unsafe.As<TEnum, int>(ref comparand);
            int valueRaw = Unsafe.As<TEnum, int>(ref value);
            ref int locationRaw = ref Unsafe.As<TEnum, int>(ref location);
            int returnRaw = Interlocked.CompareExchange(ref locationRaw, valueRaw, comparandRaw);
            return Unsafe.As<int, TEnum>(ref returnRaw);
        }

        static TEnum CompareExchange64Bit(ref TEnum location, TEnum value, TEnum comparand)
        {
            long comparandRaw = Unsafe.As<TEnum, long>(ref comparand);
            long valueRaw = Unsafe.As<TEnum, long>(ref value);
            ref long locationRaw = ref Unsafe.As<TEnum, long>(ref location);
            long returnRaw = Interlocked.CompareExchange(ref locationRaw, valueRaw, comparandRaw);
            return Unsafe.As<long, TEnum>(ref returnRaw);
        }
    }
}