简介编辑:
我们知道 C# 中的 ref 参数传递对变量的引用,允许在调用的方法中更改外部变量本身。但是引用是否像 C 指针那样处理(每次访问该参数时读取原始变量的当前内容,并在每次修改参数时更改原始变量),或者被调用的方法是否可以依赖于一致的引用通话时长?前者带来了一些线程安全问题。尤其:
我在 C# 中编写了一个静态方法,它通过引用传递一个对象:
public static void Register(ref Definition newDefinition) { ... }
调用者提供了一个已完成但尚未注册的Definition
对象,经过一些一致性检查后,我们“注册”了他们提供的定义。但是,如果已经存在具有相同键的定义,则无法注册新键,而是将其引用更新Definition
为该键的“官方”。
我们希望这是严格线程安全的,但我想到了一个病态的场景。假设客户端(使用我们的库)以非线程安全的方式共享引用,例如使用静态成员而不是局部变量:
private static Definition riskyReference = null;
如果一个线程设置riskyReference = new Definition("key 1");
、填写定义并调用我们Definition.Register(ref riskyReference);
,而另一个线程也决定设置riskyReference = new Definition("key 2");
,我们是否保证在我们的 Register 方法中,newDefinition
我们正在处理的引用不会被其他线程修改(因为对对象被复制进来并在我们返回时被复制出来?),或者其他线程可以在执行过程中替换我们身上的对象(如果我们引用指向原始存储位置的指针???),因此打破我们的健全性检查?
请注意,这与对底层对象本身的更改不同,这对于引用类型(类)当然是可能的,但可以通过该类中的适当锁定来轻松防止。但是,我们不能保护对外部客户端变量空间本身的更改!我们必须在方法顶部制作自己的参数副本并覆盖底部的参数(例如),但考虑到处理不安全的参考。
所以,我倾向于认为引用可能会被编译器复制进和复制出,以便该方法处理对原始对象的一致引用(直到它在需要时更改自己的引用),无论可能发生什么发生在其他线程上的原始位置。但是,在文档和 ref 参数的讨论中,我们很难找到关于这一点的明确答案。
任何人都可以通过明确的引用来缓解我的担忧吗?
Edit for conclusion:
Having confirmed it with a multi-threaded code example (Thanks Marc!) and thinking about it further, it makes sense that it is indeed the not-automatically-threadsafe behavior which I was worred about. One point of "ref" is to pass large structs by reference rather than copy them. Another reason is that you might want to set up a long-term monitoring of a variable and need to pass a reference to it which will see changes to the variable (eg. changing between null and a live object), which an automatic copy-in/copy-out would not allow for.
So, to make our Register
method robust against client insanity, we could implement it like:
public static void Register(ref Definition newDefinition) {
Definition theDefinition = newDefinition; // Copy in.
//... Sanity checks, actual work...
//...possibly changing theDefinition to a new Definition instance...
newDefinition = theDefinition; // Copy out.
}
They'd still have their own threading issues as far as what they end up getting, but at least their insanity wouldn't break our own sanity-checking process and possibly slip a bad state past our checks.