2011-09-21 86 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; 
    } 
}