2010-12-03 10 views
6

Me gustaría usar la clase .NET de Lazy<T> para implementar el almacenamiento en caché seguro de subprocesos. Supongamos que tenemos la siguiente configuración:Puede (¿debería?) Lazy <T> ser utilizado como una técnica de almacenamiento en caché?

class Foo 
{ 
    Lazy<string> cachedAttribute; 

    Foo() 
    { 
     invalidateCache(); 
    } 

    string initCache() 
    { 
     string returnVal = ""; 
     //CALCULATE RETURNVAL HERE 
     return returnVal; 
    } 

    public String CachedAttr 
    { 
     get 
     { 
      return cachedAttribute.Value; 
     } 
    } 

    void invalidateCache() 
    { 
     cachedAttribute = new Lazy<string>(initCache, true); 
    } 
} 

Mis preguntas son:

  1. Que este trabajo en absoluto?
  2. ¿Cómo funcionaría el bloqueo?

Siento que me falta un candado en algún lugar cerca del invalidateCache, pero por mi vida no puedo entender de qué se trata.

Estoy seguro de que hay un problema con esto en alguna parte, simplemente no he resuelto dónde.

[EDIT]

Ok, así que parece que tenía razón, había cosas que no había pensado. Si un hilo ve un caché obsoleto sería algo muy malo, por lo que parece que "Lazy" no es lo suficientemente seguro. Sin embargo, se accede a la propiedad mucho, por lo que estaba realizando una optimización pre-madura con la esperanza de poder aprender algo y tener un patrón para utilizar en el futuro para el almacenamiento en caché seguro para subprocesos. Seguiré trabajando en eso.

P.S .: Decidí hacer que el objeto thread-un-safe y tener acceso al objeto se controlara cuidadosamente en su lugar.

Respuesta

8

Bueno, no es seguro para subprocesos en que un subproceso todavía podría ver el valor anterior después de que otro subproceso vea el nuevo valor después de la invalidación, porque el primer subproceso podría no haber visto el cambio a cachedAttribute. En teoría, esa situación podría perpetuar para siempre, a pesar de que es bastante improbable :)

Usando Lazy<T> como una caché de inmutables valores parece una idea mejor para mí - más de acuerdo con la forma en que se pretendía - pero si se puede hacer frente a la posibilidad de utilizar un viejo valor "invalidado" por un período arbitrariamente largo en otro hilo, creo que esto estaría bien.

+0

Sí, los valores invalidados no son buenos en esta situación (al menos no valores arbitrariamente largos). Gracias. – Crisfole

3

cachedAttribute es un recurso compartido que debe protegerse frente a modificaciones concurrentes.

protegerlo con una lock:

private readonly object gate = new object(); 

public string CachedAttr 
{ 
    get 
    { 
     Lazy<string> lazy; 
     lock (gate)       // 1. Lock 
     { 
      lazy = this.cachedAttribute; // 2. Get current Lazy<string> 
     }         // 3. Unlock 
     return lazy.Value     // 4. Get value of Lazy<string> 
              // outside lock 
    } 
} 

void InvalidateCache() 
{ 
    lock (gate)        // 1. Lock 
    {          // 2. Assign new Lazy<string> 
     cachedAttribute = new Lazy<string>(initCache, true); 
    }          // 3. Unlock 
} 

o utilizar Interlocked.Exchange:

void InvalidateCache() 
{ 
    Interlocked.Exchange(ref cachedAttribute, new Lazy<string>(initCache, true)); 
} 

volatile podría funcionar tan bien en este escenario, pero hace que me duela la cabeza.

+0

Sugeriría poner la recuperación de 'cachedAttribute' en el bloqueo, pero permitir que la evaluación de' Value' ocurra * fuera * del bloqueo. –

+0

@Jon Skeet: Buen punto. Fijo. – dtb

+0

Gracias por la respuesta rápida. Multi-threading hace que me duela la cabeza, así que no te preocupes, ¡estás en buena compañía si tus preocupaciones son casi volátiles! ¿Alguna de estas situaciones evitaría leer valores antiguos? – Crisfole

Cuestiones relacionadas