通过反射以原子方式读取字段值
Atomically reading values of fields via reflection
假设我有以下 C# 声明:
struct Counters
{
public long a;
public long b;
public long c;
}
是否可以遍历 Counters
的给定实例的字段并使用 Interlocked.Read()
读取它们的值?即
Counters counters;
foreach (var counter in typeof(Counters).GetFields())
{
var value = Interlocked.Read(???);
}
实例字段的值是针对每个对象的,因此您需要获取特定对象的值。
Counters counter1 = new Counter() { a = 40; b = 50; c = 60; }
Type counterType = counter1.GetType();
foreach (var field in counterType.GetFields())
{
var value = Interlocked.Read(field.GetValue(counter1));
}
在这种情况下,我们获取 counter1
字段的值,而不是任何其他结构实例。
您不能直接使用 Interlocked.Read
,因为它需要一个 ref
参数 - 而且您不能直接使用所需的 System.Int64&
类型。
所以,回到反思:
// You can keep this static in some helper class
var method = typeof(Interlocked).GetMethod("Read", new []{ typeof(long).MakeByRefType() });
var result = (long)method.Invoke(null, new object[] { counter.GetValue(instance) });
编辑: 这也不起作用,我搞砸了我的测试。您仍在阅读不是自动生成的副本。
不过这会起作用:
public delegate long AtomicReadDelegate<T>(ref T instance);
public static AtomicReadDelegate<T> AtomicRead<T>(string name)
{
var dm = new DynamicMethod(typeof(T).Name + "``" + name + "``AtomicRead", typeof(long),
new [] { typeof(T).MakeByRefType() }, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, typeof(T).GetField(name));
il.Emit(OpCodes.Call,
typeof(Interlocked).GetMethod("Read", new [] { typeof(long).MakeByRefType() }));
il.Emit(OpCodes.Ret);
return (AtomicReadDelegate<T>)dm.CreateDelegate(typeof(AtomicReadDelegate<T>));
}
private readonly AtomicReadDelegate<Counters>[] _allTheReads =
new []
{
AtomicRead<Counters>("a"),
AtomicRead<Counters>("b"),
AtomicRead<Counters>("c")
};
public static void SomeTest(ref Counters counters)
{
foreach (var fieldRead in _allTheReads)
{
var value = fieldRead(ref counters);
Console.WriteLine(value);
}
}
您可能想要缓存从 AtomicRead
获得的委托 - 它们可以安全地重复使用。一个简单的并发字典就可以正常工作。
别忘了这只支持long
s;如果您还需要自动读取其他类型,则需要使用 Interlocked.CompareExchange
(当然,除了引用和 int
s - 尽管根据您的代码,您可能需要一些内存屏障,即使在那种情况)。
如果您确实需要一个原子长,那么最好使用 atomics.net library(可通过 NuGet 获得)。
如果您只需要读取线程中传递的结构的值,那么读取是安全的,因为它是按值传递的。但是,如果它是通过引用传递的,或者如果您使用 unsafe/native 代码,那么最好说出您到底想要实现什么。
假设我有以下 C# 声明:
struct Counters
{
public long a;
public long b;
public long c;
}
是否可以遍历 Counters
的给定实例的字段并使用 Interlocked.Read()
读取它们的值?即
Counters counters;
foreach (var counter in typeof(Counters).GetFields())
{
var value = Interlocked.Read(???);
}
实例字段的值是针对每个对象的,因此您需要获取特定对象的值。
Counters counter1 = new Counter() { a = 40; b = 50; c = 60; }
Type counterType = counter1.GetType();
foreach (var field in counterType.GetFields())
{
var value = Interlocked.Read(field.GetValue(counter1));
}
在这种情况下,我们获取 counter1
字段的值,而不是任何其他结构实例。
您不能直接使用 Interlocked.Read
,因为它需要一个 ref
参数 - 而且您不能直接使用所需的 System.Int64&
类型。
所以,回到反思:
// You can keep this static in some helper class
var method = typeof(Interlocked).GetMethod("Read", new []{ typeof(long).MakeByRefType() });
var result = (long)method.Invoke(null, new object[] { counter.GetValue(instance) });
编辑: 这也不起作用,我搞砸了我的测试。您仍在阅读不是自动生成的副本。
不过这会起作用:
public delegate long AtomicReadDelegate<T>(ref T instance);
public static AtomicReadDelegate<T> AtomicRead<T>(string name)
{
var dm = new DynamicMethod(typeof(T).Name + "``" + name + "``AtomicRead", typeof(long),
new [] { typeof(T).MakeByRefType() }, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, typeof(T).GetField(name));
il.Emit(OpCodes.Call,
typeof(Interlocked).GetMethod("Read", new [] { typeof(long).MakeByRefType() }));
il.Emit(OpCodes.Ret);
return (AtomicReadDelegate<T>)dm.CreateDelegate(typeof(AtomicReadDelegate<T>));
}
private readonly AtomicReadDelegate<Counters>[] _allTheReads =
new []
{
AtomicRead<Counters>("a"),
AtomicRead<Counters>("b"),
AtomicRead<Counters>("c")
};
public static void SomeTest(ref Counters counters)
{
foreach (var fieldRead in _allTheReads)
{
var value = fieldRead(ref counters);
Console.WriteLine(value);
}
}
您可能想要缓存从 AtomicRead
获得的委托 - 它们可以安全地重复使用。一个简单的并发字典就可以正常工作。
别忘了这只支持long
s;如果您还需要自动读取其他类型,则需要使用 Interlocked.CompareExchange
(当然,除了引用和 int
s - 尽管根据您的代码,您可能需要一些内存屏障,即使在那种情况)。
如果您确实需要一个原子长,那么最好使用 atomics.net library(可通过 NuGet 获得)。 如果您只需要读取线程中传递的结构的值,那么读取是安全的,因为它是按值传递的。但是,如果它是通过引用传递的,或者如果您使用 unsafe/native 代码,那么最好说出您到底想要实现什么。