【问题标题】:C# Concurrent Dictionary Object Read/Update Thread Safety QuestionC#并发字典对象读取/更新线程安全问题
【发布时间】:2020-12-24 16:58:42
【问题描述】:

在下面的代码中,我有一个并发字典,用于存储单个键/值对,其中值是字符串的集合。

我将从不同的线程读取和更新这个单一键/值对中的字符串。

我知道,如果一个线程在另一个线程可能完成读取之前更改了一个值,那么并发字典并不是完全线程安全的。但同样我不确定字符串值是否真的进入这个话题,有人可以建议吗?

还值得一提的是,虽然我把这个“GetRunTimeVariables”方法放到了一个依赖注入的接口中,但由于应用程序启动和OIDC事件登录/退出的阶段,我实际上不能一直使用DI来访问这个方法。我需要访问不能使用依赖注入的类中的字典值,所以本质上我可以在应用程序的整个生命周期中根据需要从任何方式访问这个字典。

最后我不确定将这个方法推入接口是否有任何好处,另一个选项只是每次我需要它时都对这个类进行新的引用,对此的一些想法将不胜感激。

public class RunTimeSettings : IRunTimeSettings
{
    // Create a new static instance of the RunTimeSettingsDictionary that is used for storing settings that are used for quick
    // access during the life time of the application. Various other classes/threads will read/update the parameters.
    public static readonly ConcurrentDictionary<int, RunTimeVariables> RunTimeSettingsDictionary = new ConcurrentDictionary<int, RunTimeVariables>();

    public object GetRunTimeVariables()
    {
        dynamic settings = new RunTimeVariables();

        if (RunTimeSettingsDictionary.TryGetValue(1, out RunTimeVariables runTimeVariables))
        {
            settings.Sitename = runTimeVariables.SiteName;                
            settings.Street = runTimeVariables.Street;
            settings.City = runTimeVariables.City;
            settings.Country = runTimeVariables.Country;
            settings.PostCode = runTimeVariables.PostCode;
            settings.Latitude = runTimeVariables.Latitude;
            settings.Longitude = runTimeVariables.Longitude;
        }

        return settings;
    }
}

字符串值的类:

public class RunTimeVariables
{
    public bool InstalledLocationConfigured { get; set; }
    public string SiteName { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string PostCode { get; set; }
    public string Latitude { get; set; }
    public string Longitude { get; set; }
}

【问题讨论】:

  • 如果我没记错的话TryGetValue 不是线程安全的,但需要更多调查 (docs.microsoft.com/en-us/dotnet/api/…)
  • 人们会假设“尝试做某事”肯定比说“做某事”更安全,嗯,有兴趣了解更多信息。试图理解官方的 MS 文档很少有帮助
  • @ipinak ???一切都是线程安全的。唯一的问题是,如果您尝试执行 TryGetValue 并且在失败时执行明确的 AddTryGetValue的结果只在被检索到的那一刻有效,不保证前一瞬间或后一瞬间有效。

标签: c# .net-core concurrentdictionary


【解决方案1】:

System.String 类型(C# 的经典 strings)是不可变的。没有人可以修改String 的“内容”。任何人都可以将属性引用设为不同 String

但实际上,您在这里遇到的唯一问题是各种值可能会不同步。您有各种相关的属性。如果一个线程正在修改对象而另一个线程正在读取它,则读取线程可以看到“旧”版本的一些属性和“新”版本的一些属性。如果对象一旦写入ConcurrentDictionary 并没有改变(至少作为业务规则是“不可变的”),这不是一个大问题。显然,正确的解决方案是让 RuntimeVariables 成为一个不可变对象(例如,仅由在实例化时初始化的 readonly 字段组成)

【讨论】:

  • 嗨,我不明白你的解释的后半部分,但值得我一提的是,这个字符串值集合只是用于记录,所以如果在一个线程到另一个线程之间的值,它只会影响 1qty 日志事务。你觉得使用我的界面没有意义吗?实际上只需要更少的代码行就可以在需要时新建类...thx
  • @OJB1 将它放在接口中将更容易模拟它以进行单元测试,至少对于可单元测试的类以及字典的值由单元测试/和/或无法从正常来源加载字典的值(可能是因为单元测试无权访问数据库/具有这些值的远程服务器)
  • @obj1 解释的哪一部分不清楚?从哪个词到哪个词?
  • 你的最后一句话,你能举一个我应该做的更正的例子吗?谢谢
  • @OJB1 在 C# 9.0 中将有一个新功能,"Init Only Setters"。如果没有 C# 9.0,您将使用构建器模式,使用可变类(如 StringBuilder),该类具有创建不可变对象的方法(返回 string.ToString())。对于不可变对象,在您读取对象时,另一个线程不可能更改该对象。如果要更改对象,则必须使用新值创建一个新对象,然后在 dict 中切换对象。
猜你喜欢
  • 2012-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多