C# 以下线程是否安全?

C# 以下线程是否安全?,c#,thread-safety,C#,Thread Safety,我有以下代码: public class Info { public int Data { get; set; } } public class Updater { private static readonly object lockObject = new object(); private Info myInfo= new Info(); public Info MyInfo { get {

我有以下代码:

public class Info
{
    public int Data { get; set; }
}

public class Updater
{
    private static readonly object lockObject = new object();
    private Info myInfo= new Info();

    public Info MyInfo
    {
        get
        {
            lock (lockObject)
            {
                return myInfo;
            }
        }
    }

    public void UpdateInfo()
    {
        lock (lockObject)
        {
            myInfo.Data = ReadFromExternalDevice();
        }
    }        
}
我有一个
Updater
的实例,它可以从两个独立的线程访问

Updater updater = new Updater();
线程1定期调用
UpdateInfo()

线程2定期从
Info
属性的
Data
属性中读取数据

int latestData = updater.MyInfo.Data;
上述代码在任何地方都接近线程安全吗?

想象一下

  • 第一个线程获取
    MyInfo
    (并在
    返回MyInfo;
    后离开锁)
  • 第一个线程开始读取
    MyInfo
    properties
  • 第二个线程开始写入
    MyInfo
  • 第一个线程继续读取
    MyInfo
    (部分更改!)属性
在这种情况下,代码不是线程安全的。即使
MyInfo
只有一个
int
字段,也不是线程安全的:

典型实现使用的是ReaderWriterLockSlim

  • 读取第一个线程时,
    EnterReadLock()
    将(克隆)
    MyInfo
    读入局部变量(或使用
    MyInfo
    执行所有必需的过程),然后
    exitradlock()
  • 写入第二个线程时
    EnterWriteLock()
    写入,然后
    ExitWriteLock()

如果您只关心读取
数据
属性,那么此实现是。。。比如说。。。线程足够安全

32位值的读写操作在C#中是原子的,因此在
updater.MyInfo.Data
中任何时候都不会有奇怪的中间值。如果更新了更复杂的属性,则不再适用。线程2可能仅部分更新了这些属性,而线程1仍在读取相同的
MyInfo

updater.UpdateInfo();
但是如果你想做类似的事情

if (updater.MyInfo.Data == 5)
    updater.MyInfo.Data = 7;
在不同的线程中,这不再保证按照预期的方式运行。因为另一个线程可能在检查和赋值之间更改了
数据的值


您可能需要研究一种线程安全的方法来更新变量。

我要注意,从锁内执行
ReadFromExternalDevice()
可能是个坏主意,因为这会增加您在锁中的时间,并增加争用的机会。将其保存到本地并在锁中进行变异。在没有明确说明您的意思的情况下讨论线程安全是毫无意义的;你想防范什么样的情况?例如,即使没有锁,也不会有损坏的读取。即使不是预期用途,您也可以通过
volatile
定期读取或通过
Thread.volatireRead
强制读取来实现您想要的一切。只要
数据
是int,他就不必担心部分写入。尽管形式仍然很糟糕,但您是正确的。“读入局部变量”对该引用类型没有帮助。您还需要克隆属性(
Data
),因为Thread2不会更改
MyInfo
的引用,只会更改该实例中的属性。@RenéVogt:谢谢!你说得对。应该强调的是,阅读意味着在上下文中进行克隆
if (updater.MyInfo.Data == 5)
    updater.MyInfo.Data = 7;