Interlocked.CompareExchange with enum

前端 未结 5 1717
北海茫月
北海茫月 2020-12-31 05:15

I\'m trying to use Interlocked.CompareExchange with this enum:

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

The following code do

相关标签:
5条回答
  • 2020-12-31 05:27

    To make it simple, no :-)

    Sadly C#/.NET consider enums as full type, partially disconnected from their base type. Every time you try to do something "fancy" on an enum you encounter some barrier.

    0 讨论(0)
  • 2020-12-31 05:32

    Using System.Runtime.CompilerServices.Unsafe

    Here's a pretty good related answer going into depth.

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-31 05:41

    It's possible from IL, and it's possible to create a helper method for this that can be used from 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);
        }
    }
    

    Output:

    x: Y
    y: X
    

    This just forwards the arguments to the correct Interlocked.Exchange overload. It fails badly if T isn't really an enum type, or its underlying type doesn't have an Interlocked.Exchange overload.

    The generated IL is verifiable, at least according to PEVerify, as can be checked by making this use AssemblyBuilder and saving the result to a file.

    0 讨论(0)
  • 2020-12-31 05:51

    Interlocked operations on enum are no problem:

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

    See my full answer and discussion at https://stackoverflow.com/a/5589515/147511

    0 讨论(0)
  • 2020-12-31 05:54

    But are there better ways to do this?

    I use a class instead of 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()));
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题