[sửa:]Mea culpa và lời xin lỗi đến @AnorZaken từ câu trả lời của tôi cũng tương tự như mình. Tôi thành thật không nhìn thấy nó trước khi gửi bài của tôi. Tôi sẽ giữ điều này ngay bây giờ trong trường hợp văn bản và giải thích của tôi hữu ích hoặc có thông tin chi tiết bổ sung, nhưng tín dụng cho công việc trước đó đúng cách sẽ được chuyển đến Anor.
Mặc dù tôi có another solution trên trang này, một số người có thể quan tâm đến một cách tiếp cận hoàn toàn khác. Dưới đây, tôi đưa ra một DynamicMethod
mà thực hiện Interlocked.CompareExchange
cho bất kỳ 32 hoặc 64-bit blittable loại, trong đó bao gồm bất kỳ tùy chỉnh Enum
loại, các kiểu primitive mà được xây dựng trong phương pháp quên (uint
, ulong
), và thậm chí bạn riêng ValueType
trường - chừng nào bất kỳ trong số này là dword (4-byte, tức là, int
, System.Int32
) hoặc qword (8-byte, long
, System.Int64
) kích thước. Ví dụ, sau đây Enum
loại sẽ không làm việc kể từ khi nó chỉ định kích thước không phải mặc định, byte
:
enum ByteSizedEnum : byte { Foo } // no: size is not 4 or 8 bytes
Như với hầu hết DynamicMethod triển khai của runtime tạo IL, các C# đang isn Thật tuyệt vời khi nhìn thấy, nhưng đối với một số người, mã nguồn gốc JITted IL và kiểu dáng đẹp trang nhã tạo nên điều đó. Ví dụ, trái ngược với phương pháp khác tôi đăng, phương pháp này không sử dụng mã số unsafe
C#.
Để cho phép tự động suy luận của các loại chung tại địa điểm cuộc gọi, tôi quấn helper trong một lớp học 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>));
}
};
Về mặt kỹ thuật, phía trên là tất cả các bạn cần. Bây giờ bạn có thể gọi CmpXchgIL<T>.CmpXchg(...)
trên bất kỳ loại giá trị thích hợp nào (như được thảo luận trong phần giới thiệu ở trên) và nó sẽ hoạt động chính xác như được xây dựng trong Interlocked.CompareExchange(...)
trong System.Threading
.Ví dụ, cho phép nói rằng bạn có một struct
chứa hai số nguyên:
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);
}
Bạn bây giờ có thể nguyên tử xuất bản 64-bit struct cũng giống như bạn mong chờ với bất kỳ hoạt độngCmpXchg. Điều này xuất bản nguyên tử hai số nguyên sao cho không thể cho một chuỗi khác nhìn thấy một cặp 'bị rách' hoặc không nhất quán. Không cần phải nói, dễ dàng làm như vậy với một cặp logic là cực kỳ hữu ích trong lập trình đồng thời, thậm chí nhiều hơn như vậy nếu bạn đưa ra một cấu trúc phức tạp mà gói nhiều trường vào 64 bit (hoặc 32) có sẵn. Dưới đây là ví dụ về trang web gọi để thực hiện việc này:
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");
Ở trên, tôi đã đề cập rằng bạn có thể dọn dẹp trang web cuộc gọi để không phải chỉ định tham số chung. Để làm điều này, chỉ cần xác định một tĩnh generic phương pháp ở một trong lớp toàn cầu chung phi bạn:
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);
}
tôi sẽ hiển thị các trang web gọi đơn giản với một ví dụ khác, lần này sử dụng một 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");
}
đối với câu hỏi ban đầu, ulong
và uint
việc trivially cũng như:
ulong ul = 888UL;
if (CmpXchg(ref ul, 999UL, 888UL) != 888UL)
throw new Exception("change not accepted");
Tôi thực sự sẽ cung cấp cho câu trả lời này được chấp nhận bởi vì tôi có thể easilt viết một CompareExchange cho UInt32 theo cách này, đó là câu hỏi ban đầu. – Martin
Thật tuyệt khi biết điều này hoạt động, nhưng tôi nghĩ rằng cảnh báo "sử dụng rủi ro của riêng bạn" cần được nhấn mạnh rất mạnh. Nếu phiên bản tiếp theo của trình biên dịch tạo ra một temp ở đây, nó sẽ âm thầm giới thiệu một lỗi có thể rất khó để theo dõi. Tôi không phải là một đơn vị thử nghiệm lớn, nhưng loại mẹo này về cơ bản đòi hỏi một. Tuy nhiên, một điều tốt để biết về. Lên tiếng bình chọn. – Gabriel
Tôi chưa thử nghiệm nó, nhưng tôi nghi ngờ điều này là có thể mà không có 'không an toàn' bằng cách sử dụng DynamicMethod & IL-generation. (Vì nó có thể cho enums, [như được hiển thị ở đây] (http://stackoverflow.com/a/18359360/533837).) Nó vẫn là một "hack", nhưng tôi không nghĩ rằng nó sẽ phụ thuộc vào việc thực hiện trình biên dịch . – AnorZaken