C#带get/set的螺纹安全
这是C#的一个详细问题 假设我有一个带有对象的类,该对象受锁保护: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; } } 我希望一个轮询线程能够查询该属性。我还希望线程偶尔更新该对象的属性,有时用户可以更新该属性,并且用
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};