Rob*_*ker 7 .net c# parameters thread-safety
编辑for intro:
我们知道C#中的ref参数传递对变量的引用,允许在被调用的方法中更改外部变量本身.但是引用的处理方式与C指针非常相似(每次访问该参数时都会读取原始变量的当前内容,并且每次修改参数时都会更改原始变量),或者被调用的方法可以依赖于一致的引用.通话时间?前者提出了一些线程安全问题.特别是:
我在C#中编写了一个静态方法,它通过引用传递一个对象:
public static void Register(ref Definition newDefinition) { ... }
Run Code Online (Sandbox Code Playgroud)
调用者提供一个已完成但尚未注册的Definition对象,经过一些一致性检查后,我们"注册"他们提供的定义.但是,如果已经存在具有相同密钥的定义,则它不能注册新密钥,而是将其引用更新Definition为该密钥的"官方" .
我们希望这是严格的线程安全,但我想到了一个病态场景.假设客户端(使用我们的库)以非线程安全的方式共享引用,例如使用静态成员而不是局部变量:
private static Definition riskyReference = null;
Run Code Online (Sandbox Code Playgroud)
如果一个线程设置riskyReference = new Definition("key 1");,填写定义,并调用我们Definition.Register(ref riskyReference);而另一个线程也决定设置riskyReference = new Definition("key 2");,我们保证在我们的Register方法中newDefinition我们正在处理的引用不会被其他线程修改我们(因为引用了对象被复制并将在我们返回时复制出来?),或者其他线程可以在执行过程中替换我们上的对象(如果我们引用指向原始存储位置的指针???)打破我们的理智检查?
请注意,这与对基础对象本身的更改不同,这对于引用类型(类)当然是可能的,但可以通过该类中的适当锁定来轻松防范.但是,我们不能保护对外部客户端变量空间本身的更改!我们必须在方法的顶部创建我们自己的参数副本并覆盖底部的参数(例如),但这对于编译器来说似乎更有意义,因为处理的是精神错乱.不安全的参考.
因此,我倾向于认为引用可以被编译器复制并复制出来,以便该方法处理对原始对象的一致引用(直到它在需要时更改自己的引用),而不管是什么发生在其他线程上的原始位置.但是我们在文档和参考参数的讨论中找不到关于这一点的确定答案.
有人可以通过明确的引用来缓解我的担忧吗?
编辑得出结论:
用多线程代码示例确认了它(感谢Marc!)并进一步思考它,它确实是我自己所担心的非自动线程安全行为."ref"的一点是通过引用传递大结构而不是复制它们.另一个原因是你可能想要设置一个变量的长期监控,并且需要传递对它的引用,这将看到变量的变化(例如,在null和活动对象之间切换),这是一个自动复制 - in/copy-out不允许.
因此,为了使我们的Register方法能够抵御客户端的疯狂,我们可以像下面这样实现它:
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.
}
Run Code Online (Sandbox Code Playgroud)
就他们最终得到的问题而言,他们仍然有自己的线程问题,但至少他们的精神错乱不会打破我们自己的理智检查过程,并可能使我们的检查失败.
使用时ref,您传递的是调用者字段/变量的地址.因此是的:两个线程可以在字段/变量上竞争 - 但前提是它们都在与该字段/变量进行通信.如果它们对同一个实例有不同的字段/变量,那么事情就是理智的(假设它是不可变的).
例如; 在下面的代码中,Register 确实看到了Mutate对变量的更改(每个对象实例实际上是不可变的).
using System;
using System.Threading;
class Foo {
public string Bar { get; private set; }
public Foo(string bar) { Bar = bar; }
}
static class Program {
static Foo foo = new Foo("abc");
static void Main() {
new Thread(() => {
Register(ref foo);
}).Start();
for (int i = 0; i < 20; i++) {
Mutate(ref foo);
Thread.Sleep(100);
}
Console.ReadLine();
}
static void Mutate(ref Foo obj) {
obj = new Foo(obj.Bar + ".");
}
static void Register(ref Foo obj) {
while (obj.Bar.Length < 10) {
Console.WriteLine(obj.Bar);
Thread.Sleep(100);
}
}
}
Run Code Online (Sandbox Code Playgroud)
不,它不是"复制,复制".相反,变量本身是有效传入的.不是值,而是变量本身.在查看同一变量的任何其他内容中,对该方法所做的更改都是可见的.
您可以在不涉及任何线程的情况下看到此信息:
using System;
public class Test
{
static string foo;
static void Main(string[] args)
{
foo = "First";
ShowFoo();
ChangeValue(ref foo);
ShowFoo();
}
static void ShowFoo()
{
Console.WriteLine(foo);
}
static void ChangeValue(ref string x)
{
x = "Second";
ShowFoo();
}
}
Run Code Online (Sandbox Code Playgroud)
这是第一,第二,第二的输出 - 对ShowFoo() 内部ChangeValue的调用表明价值foo已经改变,这正是你所关注的情况.
解决方案
让Definition一成不变的,如果它之前没有,并改变你的方法签名:
public static Definition Register(Definition newDefinition)
Run Code Online (Sandbox Code Playgroud)
然后调用者可以根据需要替换他们的变量,但是你的缓存不能被狡猾的其他线程污染.调用者会做类似的事情:
myDefinition = Register(myDefinition);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7289 次 |
| 最近记录: |