[editar:]Mea culpa y disculpas a @AnorZaken ya que mi respuesta es similar a la de él. Honestamente, no lo vi antes de publicar el mío. Lo guardaré por ahora en caso de que mi texto y mis explicaciones sean útiles o tengan ideas adicionales, pero el crédito por el trabajo previo corresponde a Anor.
Aunque tengo another solution en esta página, algunas personas podrían estar interesados en un enfoque totalmente diferente. A continuación, me importa un DynamicMethod
que implementa Interlocked.CompareExchange
para cualquiera de 32 o 64 bits Tipo de blittable, que incluye cualquier costumbre Enum
tipos, los tipos primitivos que el método integrado olvidó (uint
, ulong
), e incluso su propios ValueType
casos - tanto tiempo como cualquiera de estos son DWORD (4-bytes, es decir, int
, System.Int32
) o QWord (8-bytes, long
, System.Int64
) de tamaño. Por ejemplo, el siguiente Enum
tipo voluntad no trabajo ya que especifica un tamaño no predeterminado, byte
:
enum ByteSizedEnum : byte { Foo } // no: size is not 4 or 8 bytes
Como con la mayoría DynamicMethod implementaciones de generada en tiempo de ejecución-IL, la C# código ISN Es hermoso de contemplar, pero para algunas personas el elegante código nativo IL y elegante JITted lo compensan. Por ejemplo, a diferencia del otro método que publiqué, este no usa el código unsafe
C#.
Para permitir la inferencia automática del tipo genérico en el sitio de llamada, envolver el ayudante en una clase static
:
public static class IL<T> where T : struct
{
// generic 'U' enables alternate casting for 'Interlocked' methods below
public delegate U _cmp_xchg<U>(ref U loc, U _new, U _old);
// we're mostly interested in the 'T' cast of it
public static readonly _cmp_xchg<T> CmpXchg;
static IL()
{
// size to be atomically swapped; must be 4 or 8.
int c = Marshal.SizeOf(typeof(T).IsEnum ?
Enum.GetUnderlyingType(typeof(T)) :
typeof(T));
if (c != 4 && c != 8)
throw new InvalidOperationException("Must be 32 or 64 bits");
var dm = new DynamicMethod(
"__IL_CmpXchg<" + typeof(T).FullName + ">",
typeof(T),
new[] { typeof(T).MakeByRefType(), typeof(T), typeof(T) },
MethodInfo.GetCurrentMethod().Module,
false);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // ref T loc
il.Emit(OpCodes.Ldarg_1); // T _new
il.Emit(OpCodes.Ldarg_2); // T _old
il.Emit(OpCodes.Call, c == 4 ?
((_cmp_xchg<int>)Interlocked.CompareExchange).Method :
((_cmp_xchg<long>)Interlocked.CompareExchange).Method);
il.Emit(OpCodes.Ret);
CmpXchg = (_cmp_xchg<T>)dm.CreateDelegate(typeof(_cmp_xchg<T>));
}
};
Técnicamente, lo anterior es todo lo que necesita. Ahora puede llamar al CmpXchgIL<T>.CmpXchg(...)
en cualquier tipo de valor apropiado (como se explica en la introducción anterior), y se comportará exactamente como el Interlocked.CompareExchange(...)
incorporado en System.Threading
.Por ejemplo, digamos que usted tiene un struct
que contienen dos enteros:
struct XY
{
public XY(int x, int y) => (this.x, this.y) = (x, y); // C#7 tuple syntax
int x, y;
static bool eq(XY a, XY b) => a.x == b.x && a.y == b.y;
public static bool operator ==(XY a, XY b) => eq(a, b);
public static bool operator !=(XY a, XY b) => !eq(a, b);
}
ahora puede publicar atómicamente la estructura de 64 bits tal como era de esperar con cualquier operacióncmpxchg. Esto publica atómicamente los dos enteros, de modo que es imposible que otro subproceso vea un emparejamiento "desgarrado" o incoherente. Huelga decir que hacerlo fácilmente con un emparejamiento lógico es muy útil en la programación concurrente, más aún si se diseña una estructura elaborada que empaqueta muchos campos en los 64 (o 32) bits disponibles. He aquí un ejemplo de la llamada in situ para hacer esto:
var xy = new XY(3, 4); // initial value
//...
var _new = new XY(7, 8); // value to set
var _exp = new XY(3, 4); // expected value
if (IL<XY>.CmpXchg(ref xy, _new, _exp) != _exp) // atomically swap the 64-bit ValueType
throw new Exception("change not accepted");
encima, he mencionado que se puede poner en orden el sitio llamado al permitir la inferencia de tipos para que no tiene que especificar el parámetro genérico. Para ello, basta con definir un método estático genérica en una de sus clases globales no genéricos:
public static class my_globals
{
[DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T CmpXchg<T>(ref T loc, T _new, T _old) where T : struct =>
_IL<T>.CmpXchg(ref loc, _new, _old);
}
te muestro el sitio llamado simplificada con un ejemplo diferente, esta vez utilizando una Enum
:
using static my_globals;
public enum TestEnum { A, B, C };
static void CompareExchangeEnum()
{
var e = TestEnum.A;
if (CmpXchg(ref e, TestEnum.B, TestEnum.A) != TestEnum.A)
throw new Exception("change not accepted");
}
en cuanto a la pregunta original, ulong
y uint
trabajo trivialmente así:
ulong ul = 888UL;
if (CmpXchg(ref ul, 999UL, 888UL) != 888UL)
throw new Exception("change not accepted");
De hecho, voy a dar la respuesta aceptada porque podría escribir un CompareExchange para UInt32 de esta manera, que fue la pregunta original. – Martin
Es bueno saber que esto funciona, pero creo que su advertencia de "uso bajo su propio riesgo" debe enfatizarse muy fuertemente. Si la próxima versión del compilador generó una temperatura aquí, introduciría silenciosamente un error que podría ser muy difícil de rastrear. No soy un gran probador de unidades, pero este tipo de truco básicamente exige uno. Aún así, es bueno saberlo. Up-votado. – Gabriel
No lo he probado, pero sospecho que esto es posible sin 'inseguro' usando DynamicMethod & IL-generation. (Dado que es posible para enumeraciones, [como se muestra aquí] (http://stackoverflow.com/a/18359360/533837). Sigue siendo un "truco", pero no creo que dependa de la implementación del compilador . – AnorZaken