【问题标题】:Interlocked.CompareExchange with enumInterlocked.CompareExchange 与枚举
【发布时间】:2013-08-23 21:20:50
【问题描述】:

我正在尝试将此枚举与 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");
}

但是有没有更好的方法来做到这一点?

【问题讨论】:

  • 您展示的内容(将其视为int 并进行强制转换)基本上正是我所做的。
  • @MarcGravell:基本上?
  • 说实话,这没什么大不了的。就我个人而言,我会将其保留为 enum,但在进行交换之前将其转换。
  • @James 你不能那样做;该字段需要为 int 才能在调用中用作 ref。你不能在ref 期间施放。
  • @James:那会扼杀使用CompareExchange的理由。

标签: c# interlocked


【解决方案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 : 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);
        }
    }
}

【讨论】:

    【解决方案2】:

    Interlockedenum 上的操作没问题:

    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查看我的完整回答和讨论

    【讨论】:

      【解决方案3】:

      但是有没有更好的方法来做到这一点?

      我使用类而不是枚举:

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

      【讨论】:

      • 如果枚举作为一种策略对象来控制很多东西,这通常是一个好主意。一个症状是枚举在不同的地方有很多开关。
      【解决方案4】:

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

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

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

      【讨论】:

      • @DarthVader 为什么不呢?我从来没有理由将Interlocked.Exchange 与枚举一起使用,但我确实有其他情况,其中有一种清晰而正确的方法来做某事,CIL 允许,但 C# 不允许。在这种情况下,我认为 C# 不是适合这项工作的工具,所以我不使用 C#。
      • 多么顽皮!但我喜欢它并直接将其复制到我的代码中:) 现在,你也可以做一个Thread.VolatileRead(myEnum) 吗?
      • @EugeneBeresovsky 当然,我不明白为什么不这样做。调整我的答案来处理这个问题应该很容易。还有很多其他方法也可以添加,我认为将所有这些方法都包含在答案中不会有任何好处。 :)
      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-29
        相关资源
        最近更新 更多