【问题标题】:C# Is updating an instance by itself thread safe?C# 更新实例本身是线程安全的吗?
【发布时间】:2017-09-07 23:15:19
【问题描述】:

我有一个名为 Configuration 的类,它有几个属性,这些属性将使用从数据库加载的值进行更新。
我在Configuration 构造函数中有一个数据触发事件OnDataChanged,其中事件处理程序订阅了一个私有方法。
Configuration 的同一个实例被多个线程使用。

如果在数据库表中更新了某些内容,则触发 OnDataChanged 事件并调用私有方法,并在此私有方法中使用数据库中的最新数据更新 Configuration 属性,以便已经使用的线程该实例将具有更新的数据。

这是线程安全的吗?
如果不是,我们如何使这个线程安全?我没有使用任何锁。

编辑:

添加示例代码:

public class Configuration
{
    private GeneralConfiguration _generalConfiguration;
    private AccountConfiguration _accountConfiguration;
    private readonly SqlTableDependency<Model> _dependency;
    public Configuration()
    {
        _dependency = new SqlTableDependency<Model>("connectionstring", "dbo.Configuration");
        _dependency.OnChanged += _dependency_OnChanged;
        _dependency.Start();
    }

    private void _dependency_OnChanged(object sender, RecordChangedEventArgs<Model> e)
    {
        Init();
    }

    private Configuration Init()
    {
        DataAccess da = new DataAccess();
        List<string> configs = da.GetConfigData();
        _generalConfiguration = JsonConvert.Deserialize<GeneralConfiguration>(configs[0]);
        _accountConfiguration = JsonConvert.Deserialize<AccountConfiguration>(configs[1]);
        return this;
    }

    public GeneralConfiguration GeneralConfiguration { get { return _generalConfiguration; } }

    public AccountConfiguration AccountConfiguration { get { return _accountConfiguration; } }
}

【问题讨论】:

  • 请提供代码示例
  • “我没有使用任何锁。” - 这肯定给了你一个提示?
  • 您打开自己的问题有多严重在某种程度上取决于您使用的数据类型。至少在某些版本的 .Net 中,并非所有变量读取/写入都是原子的,这意味着如果一个线程读取而另一个线程正在写入,则读取器可以获得一个部分是旧值和部分新值的值(double 和 long 是一个非原子引用的例子)。 Atomicity of variable references。只需实施适当的锁定即可。

标签: c# multithreading thread-safety


【解决方案1】:

正如其他人评论的那样,您的代码不太可能是线程安全的。使其线程安全的最简单方法是创建一个私有对象并使用lock 关键字——假设您没有使用任何async 代码。

public class Configuration {
   private object sync = new object();
   private int someSetting1;
   public int SomeSetting1 {
      get {
         lock (sync) {
            return someSetting1;
         }
      }
   }

   private decimal someSetting2;
   public decimal SomeSetting2 {
      get {
         lock (sync) {
            return someSetting2;
         }
      }
   }

   private void OnDataChanged() {
      lock (sync) {
         someSetting1 = loadFromDatabase();
         someSetting2 = loadFromDatabase();
      }
   }
}

如果您有多个设置需要一起更改,这会变得很糟糕。如果someSetting1someSetting2 的值相互补充(例如,用户名和密码对),则您有一个竞争条件,其中OnDataChanged 可以在调用SomeSetting1 和@987654328 之间被调用@,导致您的调用代码获得不协调的值。如果这是一个问题,那么您需要将锁定移到 Configuration 类之外。

一种方法就是在每次访问配置单例对象时锁定它——无论是更新它还是从中读取一组值。另一种避免lock 完全依赖引用的原子性的方法是让您的Configuration 类在子类中存储一组值并向该类添加公共访问器。 OnDataChanged 将换出当前值类,调用代码将获取当前值类,然后从该不可变实例中获取他们想要的所有值:

public class Configuration {
   public class Values {  
      public int SomeSetting1 { get; }
      public int SomeSetting2 { get; }
   }
   private Values currentValues;
   public Values CurrentValues {
      get {
         return currentValues;
      }
   }
   private void OnDataChanged() {
      Values newValues = new Values(getValuesFromDatabase());
      currentValues = newValues;
   }
}

调用代码会对值集执行以下操作:

var values = configuration.CurrentValues;
doSomething(values.SomeSetting1, values.SomeSetting2);

【讨论】:

  • 谢谢蒂姆。在我上面的示例代码中,每当触发 OnDataChanged 事件时,我都会通过 Init 函数更新私有 var。这样可以确保没有线程可以更新实例本身以外的属性值。我这样做是因为在我当前的设计中,我所有的后台工作进程都会在服务启动时从数据库中读取配置值(配置实例化为通过 DI 容器的单例),一旦启动,如果数据库中的任何配置值发生变化,那么我已经重新启动所有我的服务。相反,我希望服务能够动态了解更改。有什么建议吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-12
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多