C#带get/set的螺纹安全

C#带get/set的螺纹安全,c#,locking,properties,thread-safety,C#,Locking,Properties,Thread Safety,这是C#的一个详细问题 假设我有一个带有对象的类,该对象受锁保护: Object mLock = new Object(); MyObject property; public MyObject MyProperty { get { return property; } set { property = value; } } 我希望一个轮询线程能够查询该属性。我还希望线程偶尔更新该对象的属性,有时用户可以更新该属性,并且用

这是C#的一个详细问题

假设我有一个带有对象的类,该对象受锁保护:

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         return property;
    }
    set { 
         property = value; 
    }
}
我希望一个轮询线程能够查询该属性。我还希望线程偶尔更新该对象的属性,有时用户可以更新该属性,并且用户希望能够看到该属性

以下代码是否会正确锁定数据

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         lock (mLock){
             return property;
         }
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}
我的意思是,如果我想打电话

MyProperty.Field1 = 2;
或者不管怎样,在我更新时字段会被锁定吗?由equals运算符完成的设置是否在“get”函数的作用域内,或者“get”函数(以及锁)是否首先完成,然后调用设置,然后调用“set”,从而绕过锁

编辑:既然这显然不起作用,那怎么办呢?我是否需要执行以下操作:

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         MyObject tmp = null;
         lock (mLock){
             tmp = property.Clone();
         }
         return tmp;
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}
这或多或少只是确保我只能访问一个副本,这意味着如果我让两个线程同时调用一个“get”,它们将以相同的Field1值开始(对吗?)。有没有一种方法可以对属性执行读写锁定?或者我应该约束自己锁定函数的各个部分,而不是数据本身


为了让这个例子有意义:MyObject是一个异步返回状态的设备驱动程序。我通过串口向它发送命令,然后设备在自己的空闲时间响应这些命令。现在,我有一个线程轮询它的状态(“你还在吗?你能接受命令吗?”),一个线程在串口等待响应(“刚得到状态字符串2,一切都好”),然后UI线程接收其他命令(“用户希望你做这件事”),并发布驱动程序的响应(“我刚刚做了这件事,现在用它更新UI”).这就是为什么我想锁定对象本身,而不是对象的字段;这将是大量的锁,a和b,不是这个类的每个设备都有相同的行为,只是一般的行为,所以如果我对锁进行个性化,我必须编写很多单独的对话框。

在您发布的代码示例中,get从来都不是pref奥默德

在更复杂的示例中:

MyProperty.Field1 = MyProperty.doSomething() + 2;
当然,假设你做了一个:

lock (mLock) 
{
    // stuff...
}
doSomething()
中,所有的锁调用都不足以保证整个对象的同步。只要
doSomething()
函数返回,锁就会丢失,然后进行添加,然后进行赋值,从而再次锁定

或者,用另一种方式编写它,你可以假装锁不是自动完成的,然后用每行一个操作重写它,更像是“机器代码”,这变得显而易见:

lock (mLock) 
{
    val = doSomething()
}
val = val + 2
lock (mLock)
{
    MyProperty.Field1 = val
}

多线程的美妙之处在于你不知道事情发生的顺序。如果你在一个线程上设置了一些东西,它可能会先发生,也可能会在get之后发生


您发布的代码在读取和写入成员时锁定该成员。如果您想处理更新值的情况,也许您应该研究其他形式的同步,例如。(查看自动/手动版本)。然后您可以告诉“轮询”线程,该值已更改,可以重新读取。

否,您的代码不会锁定对从
MyProperty
返回的对象成员的访问。它只锁定
MyProperty
本身

您的示例用法实际上是将两个操作合并为一个,大致相当于:

// object is locked and then immediately released in the MyProperty getter
MyObject o = MyProperty;

// this assignment isn't covered by a lock
o.Field1 = 2;

// the MyProperty setter is never even called in this example
简而言之,如果两个线程同时访问
MyProperty
,getter将短暂地阻塞第二个线程,直到它将对象返回到第一个线程,但随后它也会将对象返回到第二个线程。然后,两个线程都将具有对对象的完全、未锁定的访问权限

根据问题中的进一步详细信息进行编辑

我仍然不能100%确定您想要实现什么,但是如果您只是想要对对象进行原子访问,那么您就不能对对象本身拥有调用代码锁吗

// quick and dirty example
// there's almost certainly a better/cleaner way to do this
lock (MyProperty)
{
    // other threads can't lock the object while you're in here
    MyProperty.Field1 = 2;
    // do more stuff if you like, the object is all yours
}
// now the object is up-for-grabs again

这并不理想,但只要对对象的所有访问都包含在
lock(MyProperty)
部分中,那么这种方法将是线程安全的。

示例中的锁作用域位于错误的位置-它需要位于“MyObject”类的属性而不是容器的作用域中

如果MyObject my object类仅用于包含一个线程要写入的数据和另一个线程(UI线程)要读取的数据,那么您可能根本不需要setter,只需构造一次即可


也考虑在属性级别上放置锁是锁粒度的写级别;如果为了表示事务的状态(例如:总订单和总重量),可能会写入多个属性,那么最好在MyObject级别设置锁(即锁(MyObject.SyncRoot)…。

在编辑的版本中,您仍然没有提供线程安全的方式来更新MyObject。对对象属性的任何更改都需要在同步/锁定块内完成


您可以编写单独的setter来处理这个问题,但是您已经指出,由于字段数量太多,这将很困难。如果情况确实如此(而且您还没有提供足够的信息来评估这一点),一种替代方法是编写一个使用反射的setter;这将允许您传入表示字段名的字符串,您可以动态查找字段名并更新值。这将允许您拥有一个可以处理任意数量字段的setter。这不是那么简单,也不是那么有效,但它允许您处理大量的类和字段。

如果您的方法可行,并发编程将非常容易。但事实并非如此,冰山沉没了山雀
objectRef.MyProperty += 1;
public class Status
{
    private int _code;
    private DateTime _lastUpdate;
    private object _sync = new object(); // single lock for both fields

    public int Code
    {
        get { lock (_sync) { return _code; } }
        set
        {
            lock (_sync) {
                _code = value;
            }

            // Notify listeners
            EventHandler handler = Changed;
            if (handler != null) {
                handler(this, null);
            }
        }
    }

    public DateTime LastUpdate
    {
        get { lock (_sync) { return _lastUpdate; } }
        set { lock (_sync) { _lastUpdate = value; } }
    }

    public event EventHandler Changed;
}
Status status = new Status();
ManualResetEvent changedEvent = new ManualResetEvent(false);
Thread thread = new Thread(
    delegate() {
        status.Changed += delegate { changedEvent.Set(); };
        while (true) {
            changedEvent.WaitOne(Timeout.Infinite);
            int code = status.Code;
            DateTime lastUpdate = status.LastUpdate;
            changedEvent.Reset();
        }
    }
);
thread.Start();
var someObj = -1;

// Thread 1

if (someObj = -1)
    lock(someObj)
        someObj = 42;

// Thread 2

if (someObj = -1)
    lock(someObj)
        someObj = 24;
// Threads 1 & 2

if (someObj = -1)
    lock(someObj)
        if(someObj = -1)
            someObj = {newValue};