2011-09-21 15 views
5

Tengo un controlador simple ASP.NET MVC. Dentro de algunos métodos de acción, accedo a un recurso que diré que es costoso.¿Cómo puedo usar Lazy <T> en un controlador ASP.NET MVC?

Así que pensé, ¿por qué no hacerlo estático. Entonces, en lugar de hacer double checked locking, creo que puedo aprovechar el uso de Lazy<T> en .NET 4.0. Llame al servicio caro una vez en lugar de varias veces.

Por lo tanto, si este es mi código pseduo, ¿cómo puedo cambiarlo? Use Lazy<T>. Para este ejemplo contrito, usaré el File System como el recurso costoso recurso Así que con este ejemplo, en vez de obtener todos los archivos de la ruta de destino, cada vez que una solicitud llama a ese Método de Acción, esperaba usar Lazy para mantenga esa lista de archivos ... que por supuesto, hace la llamada por primera vez solamente.

Siguiente suposición: no se preocupe si se cambia el contenido. Eso está fuera de alcance, aquí.

public class FooController : Controller 
{ 
    private readonly IFoo _foo; 
    public FooController(IFoo foo) 
    { 
     _foo = foo; 
    } 

    public ActionResult PewPew() 
    { 
     // Grab all the files in a folder. 
     // nb. _foo.PathToFiles = "/Content/Images/Harro" 
     var files = Directory.GetFiles(Server.MapPath(_foo.PathToFiles)); 

     // Note: No, I wouldn't return all the files but a concerete view model 
     //  with only the data from a File object, I require. 
     return View(files); 
    } 
} 
+1

¿Qué pasa con el uso de la memoria caché ASP.NET? – tvanfosson

+1

Parece que está buscando un singleton, en lugar de instanciación lenta de un objeto. Por supuesto, puedes * usar * 'Lazy' para crear un singleton ... –

Respuesta

5

En su ejemplo, el resultado de Directory.GetFiles depende del valor de _foo, que no es estática. Por lo tanto, no puede usar una instancia estática de Lazy<string[]> como caché compartida entre todas las instancias de su controlador.

El ConcurrentDictionary<TKey, TValue> suena como algo que está más cerca de lo que desea.

// Code not tested, blah blah blah... 
public class FooController : Controller 
{ 
    private static readonly ConcurrentDictionary<string, string[]> _cache 
     = new ConcurrentDictionary<string, string[]>(); 

    private readonly IFoo _foo; 
    public FooController(IFoo foo) 
    { 
     _foo = foo; 
    } 

    public ActionResult PewPew() 
    { 
     var files = _cache.GetOrAdd(Server.MapPath(_foo.PathToFiles), path => { 
      return Directory.GetFiles(path); 
     }); 

     return View(files); 
    } 
} 
+0

¡Esta es una gran idea! Con la opción de caché (dada por Martin o Chris), es posible que más de una solicitud intente insertar en la caché -si- las solicitudes están sucediendo (más o menos) al mismo tiempo ... ¿verdad?(condición de la carrera sorta cosa) mientras que el diccionario concurrente evita que la segunda, tercera solicitud (al mismo tiempo) ejecute el recurso caro, ¿verdad? –

+0

@Pure - En realidad, no, el ConcurrentDictionary puede invocar la función "valueFactory" más de una vez si varios subprocesos intentan obtener el valor antes de que se haya agregado a la caché. ConcurrentDictionary es seguro para subprocesos (solo se agregará un valor para cada clave), pero la invocación de valueFactory no ocurre dentro de un bloqueo. – Greg

+0

El ConcurrentDictionary es seguro para subprocesos, sin embargo, el delegado pasó a GetOrAdd y AddOrUpdate se invoca fuera del bloqueo interno del diccionario. Más aquí: http://msdn.microsoft.com/en-us/library/dd997369.aspx – Paul

4

Estoy de acuerdo con Greg that Lazy <> no es apropiado aquí.

Puede intentar usar asp.net caching para almacenar en caché el contenido de una carpeta, usando _foo.PathToFiles como su clave. Esto tiene una ventaja sobre Lazy <> que puede controlar el tiempo de vida del almacenamiento en caché, por lo que volverá a cargar los contenidos, por ejemplo, todos los días o todas las semanas sin necesidad de reiniciar la aplicación.

También el almacenamiento en caché es amigable para su servidor, ya que se degradará elegantemente si no hay suficiente memoria para soportarlo.

+0

+1 para recomendar el almacenamiento en caché ASP.NET incorporado. No reinvente la rueda hasta que tenga una razón sólida para hacerlo. – Greg

+0

Sí, gracias. Me gusta tu caché en línea también, tal vez no para este escenario, pero podría ser útil para las aplicaciones de formularios de Windows. –

2

Lazy<T> funciona mejor cuando no está seguro de si va a necesitar el recurso, por lo que se carga justo a tiempo solo cuando es realmente necesario. La acción siempre va a cargar el recurso independientemente, pero debido a que es costoso, ¿es probable que quiera almacenarlo en algún lugar? Usted podría intentar algo como esto:

public ActionResult PewPew() 
{ 
    MyModel model; 
    const string cacheKey = "resource"; 
    lock (controllerLock) 
    { 
     if (HttpRuntime.Cache[cacheKey] == null) 
     { 
      HttpRuntime.Cache.Insert(cacheKey, LoadExpensiveResource()); 
     } 
     model = (MyModel) HttpRuntime.Cache[cacheKey]; 
    } 

    return View(model); 
} 
1

yo sólo tenía el mismo problema que usted describió así que creó una clase CachedLazy<T> -> permite que los valores sean compartidos entre instancias del controlador, pero con caducidad temporizada opcional y la creación de una sola vez a diferencia de ConcurrentDictionary.

/// <summary> 
/// Provides a lazily initialised and HttpRuntime.Cache cached value. 
/// </summary> 
public class CachedLazy<T> 
{ 
    private readonly Func<T> creator; 

    /// <summary> 
    /// Key value used to store the created value in HttpRuntime.Cache 
    /// </summary> 
    public string Key { get; private set; } 

    /// <summary> 
    /// Optional time span for expiration of the created value in HttpRuntime.Cache 
    /// </summary> 
    public TimeSpan? Expiry { get; private set; } 

    /// <summary> 
    /// Gets the lazily initialized or cached value of the current Cached instance. 
    /// </summary> 
    public T Value 
    { 
     get 
     { 
      var cache = HttpRuntime.Cache; 

      var value = cache[Key]; 
      if (value == null) 
      { 
       lock (cache) 
       { 
        // After acquiring lock, re-check that the value hasn't been created by another thread 
        value = cache[Key]; 
        if (value == null) 
        { 
         value = creator(); 
         if (Expiry.HasValue) 
          cache.Insert(Key, value, null, Cache.NoAbsoluteExpiration, Expiry.Value); 
         else 
          cache.Insert(Key, value); 
        } 
       } 
      } 

      return (T)value; 
     } 
    } 

    /// <summary> 
    /// Initializes a new instance of the CachedLazy class. If lazy initialization occurs, the given 
    /// function is used to get the value, which is then cached in the HttpRuntime.Cache for the 
    /// given time span. 
    /// </summary> 
    public CachedLazy(string key, Func<T> creator, TimeSpan? expiry = null) 
    { 
     this.Key = key; 
     this.creator = creator; 
     this.Expiry = expiry; 
    } 
} 
Cuestiones relacionadas