2011-09-21 54 views
5

我有一个简单的ASP.NET MVC控制器。在一些动作方法中,我访问一个资源,我会说很贵如何在ASP.NET MVC控制器中使用懒惰<T>?

所以我想,为什么不把它变成静态的。因此,我认为我可以利用.NET 4.0中的Lazy<T>来代替double checked locking。一次而不是多次拨打昂贵的服务

所以,如果这是我的pseduo代码,我该如何改变它使用Lazy<T>。 对于这个痛苦的例子,我将使用File System作为昂贵的资源 因此,在这个例子中,不是从目标路径获取所有文件,每次请求调用ActionMethod时,我都希望使用Lazy保存文件列表..这当然只是第一次打电话。

下一个假设:如果内容发生变化,请不要担心。这里超出了范围。

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

使用ASP.NET缓存有什么问题? – tvanfosson

+1

这听起来像你正在寻找一个单例,而不是懒惰的对象实例化。当然,你可以*使用懒惰''创建一个单身... –

回答

5

在您的例子中,Directory.GetFiles结果取决于_foo的价值,这也不是一成不变的。因此,您不能使用Lazy<string[]>的静态实例作为控制器所有实例之间的共享缓存。

ConcurrentDictionary<TKey, TValue>听起来像是更接近你想要的东西。

// 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

这是一个好主意!使用缓存选项(由Martin或Chris给出),可能会有多个请求尝试插入缓存 - 如果请求正在同时发生(或多或少)..对吗?(竞争条件sorta事物)而并发字典阻止第二个,第三个请求(在同一时间)执行昂贵的资源,对吧? –

+0

@Pure - 实际上,如果多个线程在添加到缓存之前尝试获取值,ConcurrentDictionary可能会多次调用“valueFactory”函数。 ConcurrentDictionary是线程安全的(每个键只会添加一个值),但是valueFactory的调用不会在锁内发生。 – Greg

+0

ConcurrentDictionary是线程安全的,但传递给GetOrAdd和AddOrUpdate的委托在字典的内部锁之外被调用。更多信息:http://msdn.microsoft.com/en-us/library/dd997369.aspx – Paul

4

我同意Greg,懒惰<>在这里不合适。

您可以尝试使用asp.net caching来缓存文件夹的内容,使用_foo.PathToFiles作为您的密钥。这比懒惰更有优势,您可以控制缓存的生命周期,以便每天或每周重新读取内容,而不需要重新启动应用程序。

缓存对您的服务器也很友善,因为如果没有足够的内存来支持它,它会优雅地降级。

+0

+1,用于推荐内置的ASP.NET缓存。除非你有充分的理由这样做,否则不要重新发明轮子。 – Greg

+0

是的谢谢。我也喜欢你的内联缓存 - 可能不适合这种情况,但可能对Windows窗体应用有用。 –

2

Lazy<T>当您不确定您是否需要该资源时,效果最佳,因此仅在实际需要时即时加载。 无论如何,该操作总是要加载资源,但由于代价很高,您可能需要将其缓存在某处?你可以尝试这样的事:

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

我只是有你这样描述的同样的问题,我创建了一个类CachedLazy<T> - >允许控制器实例之间共同的价值观,但可选的定时期满的和一次性的创作不像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; 
    } 
}