C# 持久内存缓存上的单例模式

C# 持久内存缓存上的单例模式,c#,architecture,.net-3.5,singleton,C#,Architecture,.net 3.5,Singleton,在这篇令人惊叹的文章中,我使用了我认为最好的方法,成功地使用了以下类在内存中持久化用户定义的数据(对于很少修改的数据): 每当我更新数据库时,就会调用RefreshLocalizations,因此只重建内存中存储的一部分。在调用RefreshLocalizations时,大约10个生产环境中有一个似乎表现不正常,根本不刷新,但这似乎也是间歇性的,非常奇怪 我目前的怀疑是针对单例的,我认为这项工作做得很好,所有的单元测试都证明单例机制、刷新机制和RAM性能都能按预期工作 也就是说,我认为有以下几种

在这篇令人惊叹的文章中,我使用了我认为最好的方法,成功地使用了以下类在内存中持久化用户定义的数据(对于很少修改的数据):

每当我更新数据库时,就会调用RefreshLocalizations,因此只重建内存中存储的一部分。在调用RefreshLocalizations时,大约10个生产环境中有一个似乎表现不正常,根本不刷新,但这似乎也是间歇性的,非常奇怪

我目前的怀疑是针对单例的,我认为这项工作做得很好,所有的单元测试都证明单例机制、刷新机制和RAM性能都能按预期工作

也就是说,我认为有以下几种可能性:

  • 当这位客户说他们的环境没有使用负载平衡时,他在撒谎,我不希望内存中的东西正常工作(对吗?)
  • 我测试的IIS中有一些非标准池配置(可能在Web Garden设置中?)
  • 单身汉在某种程度上失败了,但不知道如何失败
  • 有什么建议吗

    .NET 3.5因此没有太多的并行功能可用,而且目前还没有准备好使用反应式扩展

    Edit1:根据建议,getter看起来像:

    public IEnumerable<Localization> Localizations
    {
      get
      {
        lock(_localizations) {
          return _localizations ?? (_localizations = new Repository<Localization>().Get());
        }
      }
    }
    
    公共IEnumerable本地化
    {
    得到
    {
    锁定(_本地化){
    返回_localizations???(_localizations=newrepository().Get());
    }
    }
    }
    
    如果您的属性不是线程安全的,那么创建线程安全的单例是没有意义的

    您应该锁定
    \u本地化
    字段的分配,或者在单例构造函数中实例化(首选)。任何适用于单例实例化的建议都适用于此惰性实例化属性

    这同样适用于
    本地化
    的所有属性(及其属性)。如果这是一个单例,这意味着任何线程都可以随时访问它,而仅仅锁定getter也不会起任何作用

    例如,考虑这种情况:

    Thread 1 Thread 2 // both threads access the singleton, but you are "safe" because you locked 1. var loc1 = Params.Localizations; var loc2 = Params.Localizations; // do stuff // thread 2 calls the same property... 2. var value = loc1.ChunkSize; var chunk = LC.Loc("params.chunksize"); // invalidate // ...there is a slight pause here... 3. loc1.RebuildLocalizations(); // ...and gets the wrong value 4. var value = chunk.To(); 螺纹1螺纹2 //两个线程都访问单线程,但您是“安全的”,因为您已锁定 1.var loc1=参数本地化;var loc2=参数本地化; //do stuff//线程2调用相同的属性。。。 2.var值=loc1.1.1;var chunk=LC.Loc(“params.chunksize”); //无效/…此处有一个轻微的停顿。。。 3.位置1.重建本地化(); //…并得到错误的值 4.var value=chunk.To(); 如果您只是读取这些值,那么这可能无关紧要,但您可以看到使用这种方法很容易遇到麻烦

    请记住,对于线程,您永远不知道不同的线程是否会在两条指令之间执行某些操作只有简单的32位赋值是原子赋值,没有其他赋值

    这意味着,在这一行中:

    return LC.Loc("params.chunksize").To<int>();
    
    返回LC.Loc(“params.chunksize”).To();
    
    就线程而言,相当于:

    var loc = LC.Loc("params.chunksize");
    Thread.Sleep(1); // anything can happen here :-(
    return loc.To<int>();
    
    var loc=LC.loc(“params.chunksize”);
    线程。睡眠(1);//任何事情都可能发生在这里:-(
    返回loc.To();
    

    任何线程都可以跳到
    Loc
    To

    之间。要展开我的评论,以下是如何使
    本地化
    属性线程安全:

    public class Params
    {
      private object _lock = new object();
    
      private IEnumerable<Localization> _localizations;    
      public IEnumerable<Localization> Localizations
      {
        get
        {
          lock (_lock) {
             if ( _localizations == null ) {
                _localizations = new Repository<Localization>().Get();
             }
    
             return _localizations;
          }
        }
      }
    
      public void RebuildLocalizations()
      {
         lock(_lock) {
            _localizations = null;
         }
      }
    
      // other similar values coming from the DB and staying in-memory,
      // and their refresh methods
    
    }
    
    公共类参数
    {
    私有对象_lock=新对象();
    私有IEnumerable\u本地化;
    公共可数本地化
    {
    得到
    {
    锁{
    if(_localizations==null){
    _本地化=新存储库().Get();
    }
    返回本地化;
    }
    }
    }
    公共空间重建本地化()
    {
    锁{
    _本地化=空;
    }
    }
    //来自数据库并保留在内存中的其他类似值,
    //以及它们的刷新方法
    }
    
    这里是否可能存在一些线程安全问题?似乎您希望有一个锁,以防止在刷新后创建存储库的多个实例(通常称为“缓存踩踏”)从您的问题来看,似乎要发布的代码就是进行刷新的代码。我认为您上面的代码没有问题。负载平衡和IIS不应该有任何影响(但需要再次查看db刷新代码),除非您指的是多个Web服务器,在这种情况下,您需要跨它们同步刷新(在哪种情况下,只需使用外部缓存…AppFabric缓存就可以了)@F.Aquino:事实上,不是这样。很可能是两个线程几乎同时尝试访问
    本地化
    属性,导致创建了两个存储库。将此属性设置为线程安全将解决此问题。换句话说,创建实例后,您的singleton类不是线程安全的ed.正如Eric所说,肯定会有一些线程问题,因为您没有锁定_localizations成员(和??调用).但是你说数据很少更新…你确定吗?@F.Aquino我应该补充一点,你应该修改你的Singleton以解决其他海报中提到的线程问题,但我不认为这是你问题的根源。还有,你有没有理由不使用简单的静态类?如果你重新实现接口,或者需要从基类继承,但您似乎没有这样做,因此静态类可能同样有效。您的第二种方法是我从未见过的,非常有趣,因此构造函数方法不需要锁
    var loc = LC.Loc("params.chunksize");
    Thread.Sleep(1); // anything can happen here :-(
    return loc.To<int>();
    
    public class Params
    {
      private object _lock = new object();
    
      private IEnumerable<Localization> _localizations;    
      public IEnumerable<Localization> Localizations
      {
        get
        {
          lock (_lock) {
             if ( _localizations == null ) {
                _localizations = new Repository<Localization>().Get();
             }
    
             return _localizations;
          }
        }
      }
    
      public void RebuildLocalizations()
      {
         lock(_lock) {
            _localizations = null;
         }
      }
    
      // other similar values coming from the DB and staying in-memory,
      // and their refresh methods
    
    }