2012-01-11 16 views
5

Usando lo que juzgué que era el mejor de todos los mundos en el asombroso artículo Implementing the Singleton Pattern in C#, he estado usando con éxito la siguiente clase para persistir los datos definidos por el usuario en la memoria (para los muy raramente modificados datos):Patrón de Singleton en caché en memoria persistente

public class Params 
{ 
    static readonly Params Instance = new Params(); 

    Params() 
    { 
    } 

    public static Params InMemory 
    { 
    get 
    { 
     return Instance; 
    } 
    } 

    private IEnumerable<Localization> _localizations; 

    public IEnumerable<Localization> Localizations 
    { 
    get 
    { 
     return _localizations ?? (_localizations = new Repository<Localization>().Get()); 
    } 
    } 

    public int ChunkSize 
    { 
    get 
    { 
     // Loc uses the Localizations impl 
     LC.Loc("params.chunksize").To<int>(); 
    } 
    } 

    public void RebuildLocalizations() 
    { 
    _localizations = null; 
    } 

    // other similar values coming from the DB and staying in-memory, 
    // and their refresh methods 

} 

mi uso sería algo como esto:

var allLocs = Params.InMemory.Localizations; //etc 

Siempre que puedo actualizar la base de datos, los RefreshLocalizations es llamado por lo que sólo parte de mi tienda en memoria, se reconstruye. Tengo un entorno de producción único de aproximadamente 10 que parece que se comporta mal cuando se llama a RefreshLocalizations, no se actualiza en absoluto, pero esto también parece ser intermitente y muy extraño por completo.

Mi sospecha actual va hacia el singleton, que creo que hace bien el trabajo y todas las pruebas unitarias demuestran que el mecanismo de singleton, el mecanismo de actualización y el rendimiento de RAM funcionan todos como se esperaba.

Dicho esto, estoy abajo a estas posibilidades:

  1. Este cliente está mintiendo cuando dice que su entorno no está usando el equilibrio de carga, que es un entorno no estoy esperando la materia en memoria para trabajar correctamente (¿no?)
  2. Hay alguna configuración de grupo no estándar en su IIS contra la que estoy probando (¿quizás en una configuración Web Garden?)
  3. El singleton está fallando de alguna manera, pero no está seguro de cómo hacerlo.

¿Alguna sugerencia?

.NET 3.5 así no hay mucho jugo paralelo disponible, y no está listo para usar las extensiones reactivas por ahora

Edit1: de acuerdo con las sugerencias, lo haría el aspecto captador algo como:

public IEnumerable<Localization> Localizations 
{ 
    get 
    { 
    lock(_localizations) { 
     return _localizations ?? (_localizations = new Repository<Localization>().Get()); 
    } 
    } 
} 
+3

Es es posible que tenga algunos problemas de seguridad hilo aquí? Parece que le gustaría tener un candado para evitar que se creen varias instancias de su repositorio después de una actualización (comúnmente llamada "estampida de caché") –

+0

Gracias por su comentario Eric, asumí la forma en que llamo al repositorio usando la instancia de InMemory evitaría que eso suceda, ¿tiene sentido? –

+1

De tu pregunta, parece que el código para publicar sería el código que realiza la actualización. Lo que tienes arriba se ve bien para mí. El equilibrio de carga e IIS no deberían tener ningún efecto (pero de nuevo necesitaría ver el código de actualización de db), a menos que se refiera a múltiples servidores web, en cuyo caso deberá sincronizar la actualización entre ellos (en cuyo caso solo usará un caché externo). .... El caché de AppFabric sería adecuado) –

Respuesta

2

Para ampliar mi comentario, aquí es cómo se puede hacer que el hilo de seguridad Localizations propiedad:

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 

} 
+0

Eric, gracias, ¿el enfoque _lock tiene alguna ventaja de bloquear la variable de instancia en sí? –

+2

@ F.Aquino - Sí, dado que está asignando a la variable de instancia, tampoco desea bloquearla ya que su bloqueo se liberará inadvertidamente cuando reasigne el objeto. Consulte la primera respuesta sobre esta pregunta para una buena explicación: http://stackoverflow.com/questions/1287356/c-sharp-threading-lock-object –

+0

Gracias Erik, fui con su sugerencia y comenzaré el control de calidad hoy, Me gustaría agradecer a todos los que contribuyeron al hilo, comentarios sorprendentes y rápidos, lamentablemente estas grandes preguntas que veo tantas personas que se equivocan no se les pide tanto. –

2

No tiene sentido crear un singleton seguro para subprocesos, si sus propiedades no son seguras para subprocesos.

Debe bloquear la asignación del campo _localization o crear una instancia en el constructor de su singleton (prefirió). Cualquier sugerencia que se aplique a la instanciación de singleton se aplica a esta propiedad instanciada de forma lenta.

Lo mismo se aplica a todas las propiedades (y sus propiedades) de Localization. Si se trata de un Singleton, significa que cualquier hilo puede acceder a él en cualquier momento, y simplemente bloquear el getter no hará nada nuevamente.

Por ejemplo, considere este caso:

 
    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(); 

Si sólo está leyendo estos valores, entonces no podría ser importante, pero se puede ver cómo se puede conseguir fácilmente en problemas con este enfoque.

Recuerde que con el enhebrado, nunca se sabe si un hilo diferente ejecutará algo entre dos instrucciones.Las asignaciones simples de 32 bits son atómicas, nada más.

Esto significa que, en esta línea aquí:

return LC.Loc("params.chunksize").To<int>(); 

es decir, como lo que se refiere roscado, equivalente a:

var loc = LC.Loc("params.chunksize"); 
Thread.Sleep(1); // anything can happen here :-(
return loc.To<int>(); 

Cualquier hilo puede saltar entre Loc y To.

+0

Su segundo enfoque es algo que nunca he visto, muy interesante, ¿entonces el enfoque de constructor no requerirá el contenedor de bloqueo? ¿Eso también eliminaría la variable de instancia _localizations y simplemente establecería la propiedad de Localizaciones usando una auto-propiedad? –

+2

@ F.Aquino: sí y no; aún implicaría un campo de respaldo '' privado readonly' 'localizaciones', para evitar que alguien accidentalmente lo cambie, ya que no hay bloqueo. Dejar el acceso del 'set' no arreglaría nada. Pero en ese caso, no tendría la posibilidad de "reconstruir" al invalidarlo. Si esto es lo que buscas, opta por el bloqueo regular, esa es tu única opción. – Groo

+2

@ F.Aquino: He actualizado mi respuesta con información adicional. Por cierto, ¿por qué esto se llama "Localizaciones"? ¿Qué tiene con la localización realmente? – Groo

Cuestiones relacionadas