Interlocked.CompareExchange with enum

本文关键字:enum with CompareExchange Interlocked | 更新日期: 2023-09-27 18:28:32

我正试图将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");
}

但是有更好的方法吗?

Interlocked.CompareExchange with enum

这可以从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: Yy: X

这只是将参数转发给正确的Interlocked.Exchange重载。如果T不是真正的枚举类型,或者其底层类型没有Interlocked.Exchange重载,那么它会失败。

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

为了简单起见,没有:-)

遗憾的是,C#/.NET将enum视为完全类型,与它们的基本类型部分断开连接。每次你试图在enum上做一些"花哨"的事情时,你都会遇到一些障碍。

enum上的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);
}

查看我的完整答案和讨论https://stackoverflow.com/a/5589515/147511

但是有更好的方法吗?

我使用类而不是Enum:

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()));
        }
    }
}

使用System.Runtime.CompilerServices.Unsafe

下面是一个非常好的相关答案。

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 : struct, 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);
        }
    }
}