当前位置:  开发笔记 > 编程语言 > 正文

C#线程安全与get/set

如何解决《C#线程安全与get/set》经验,为你挑选了3个好方法。

这是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;

或者其他什么,我会在更新时锁定该字段?设置是由'get'函数范围内的equals运算符完成的,还是'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,并不是这个类的每个设备都有相同的行为,



1> LukeH..:

不,您的代码不会锁定对从中返回的对象成员的访问权限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)部分中,那么这种方法将是线程安全的.



2> Hans Passant..:

如果您的方法可行,并发编程将非常简单.但事实并非如此,泰坦尼克号沉没的冰山,例如,你们班级的客户这样做:

objectRef.MyProperty += 1;

读 - 修改 - 写比赛非常明显,有更糟糕的.除了使它不可变之外,你绝对没有办法让你的属性成为线程安全的.你的客户需要处理头痛问题.被迫将这种责任委托给最不可能做到正确的程序员是并发编程的致命弱点.



3> Matt Davis..:

正如其他人指出的那样,一旦从getter返回对象,就无法控制谁访问对象以及何时访问对象。要执行您想做的事情,您需要在对象本身内部放置一个锁。

也许我不了解完整情况,但是根据您的描述,听起来好像您不一定需要为每个字段都加锁。如果您只是通过getter和setter读写一组字段,则可能只需为这些字段加一个锁即可。很明显,您可能会不必要地以这种方式序列化线程的操作。但是同样,根据您的描述,这听起来也不像您正在积极地访问该对象。

我还建议使用事件而不是使用线程来轮询设备状态。使用轮询机制,每次线程查询设备时,您将被锁定。使用事件机制,一旦状态改变,对象将通知所有侦听器。届时,您的“轮询”线程(将不再进行轮询)将唤醒并获得新状态。这将更加有效。

举个例子...

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();

推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有