2009-08-19 107 views
4

我創建了一個超級簡單的控制檯應用程序來測試企業庫緩存應用程序塊,行爲令人困惑。我希望我搞定了一些在設置中很容易修復的東西。爲了測試目的,我將每個項目在5秒後過期。Microsoft企業庫緩存應用程序塊不是線程安全的?

基本設置 - 「每一秒挑0和2之間的數字。如果緩存中不已經擁有了它,把它放在那裏 - 否則只是從緩存中抓住它做這個lock語句裏面保證線程安全

的app.config:

<configuration> 
    <configSections> 
    <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> 
    </configSections> 
    <cachingConfiguration defaultCacheManager="Cache Manager"> 
    <cacheManagers> 
     <add expirationPollFrequencyInSeconds="1" maximumElementsInCacheBeforeScavenging="1000" 
     numberToRemoveWhenScavenging="10" backingStoreName="Null Storage" 
     type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
     name="Cache Manager" /> 
    </cacheManagers> 
    <backingStores> 
     <add encryptionProviderName="" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
     name="Null Storage" /> 
    </backingStores> 
    </cachingConfiguration> 
</configuration> 

C#?

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Microsoft.Practices.EnterpriseLibrary.Common; 
using Microsoft.Practices.EnterpriseLibrary.Caching; 
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     public static ICacheManager cache = CacheFactory.GetCacheManager("Cache Manager"); 
    static void Main(string[] args) 
     { 
      while (true) 
      { 
       System.Threading.Thread.Sleep(1000); // sleep for one second. 
       var key = new Random().Next(3).ToString(); 
       string value; 
       lock (cache) 
       { 
        if (!cache.Contains(key)) 
        { 
         cache.Add(key, key, CacheItemPriority.Normal, null, new SlidingTime(TimeSpan.FromSeconds(5))); 
        } 
        value = (string)cache.GetData(key); 
       } 
       Console.WriteLine("{0} --> '{1}'", key, value); 
       //if (null == value) throw new Exception(); 
      } 
     } 
    } 
} 

輸出 - 我如何防止返回空值緩存

2 --> '2' 
1 --> '1' 
2 --> '2' 
0 --> '0' 
2 --> '2' 
0 --> '0' 
1 --> '' 
0 --> '0' 
1 --> '1' 
2 --> '' 
0 --> '0' 
2 --> '2' 
0 --> '0' 
1 --> '' 
2 --> '2' 
1 --> '1' 
Press any key to continue . . . 
+0

情況下,[緩存應用程序塊(http://msdn.microsoft.com/en-us /library/ff664753%28v=PandP.50%29.aspx)「以線程安全的方式執行」。 – felickz 2012-01-04 16:21:53

+0

看來,如果item已過期,Contains()只會返回true。但GetData()在這種情況下返回null。如果將該條件更改爲if((value = GetData(...))== null)一切正常 – 2014-06-17 15:25:31

回答

7

我注意到,在前5次循環迭代期間(即5秒),只要沒有訪問過該項目,您似乎就會從緩存中取回null。這可能與您的5秒到期時間有關嗎?

這似乎不大可能,但也許你有競爭條件,並且項目將從Contains檢查和GetData檢索之間的高速緩存中剔除。

試試這個變化,看它是否在輸出任何區別:

while (true) 
{ 
    System.Threading.Thread.Sleep(1000); 

    var key = new Random().Next(3).ToString(); 
    string value; 

    lock (cache) 
    { 
     value = (string)cache.GetData(key); 
     if (value == null) 
     { 
      value = key; 
      cache.Add(key, value, CacheItemPriority.Normal, null, 
       new SlidingTime(TimeSpan.FromSeconds(5))); 
     } 
    } 
    Console.WriteLine("{0} --> '{1}'", key, value); 
} 
+0

我明白爲什麼人們會做這個,但老實說,你會認爲緩存足夠不會讓你通過這個廢話。老實說,我只是想做'GetData'並提供一個RefreshAction,並且用它來做 – AlanR 2009-08-20 11:32:39

1

雖然這可能不是解決您的特定問題,雙重檢查鎖定通常建議...

if (!cache.Contains(key)) 
{ 
    lock(mylockobj) 
    { 
     if (!cache.Contains(key)) 
     { 
      cache.Add(key, key) 
     } 
    } 
} 

也可能考慮CacheItemRemovedCallback。

+2

雙重鎖定是一個壞習慣,不推薦,因爲它不工作 – Onkelborg 2011-04-14 15:21:32

10

你們看到的是,你的CacheItem了應有的5秒SlidingTime過期失效。

返回緩存值之前,GetData方法進行檢查,看是否CacheItem已過期。如果它已過期,CacheItem將從緩存中刪除,並返回null。但是,對Contains的調用將返回true,因爲即使CacheItem已到期,CacheItem仍在緩存中。這似乎是設計。考慮到這一點,最好不要緩存空值來表示沒有數據,因爲你無法從實際緩存的null值中辨別出過期的CacheItem。

假設你不緩存爲空值,然後盧克的解決方案應該適合你:

value = cache.GetData(key) as string; 

// If null was returned then it means that there was no item in the cache 
// or that there was an item in the cache but it had expired 
// (and was removed from the cache) 
if (value == null) 
{ 
    value = key; 
    cache.Add(key, value, CacheItemPriority.Normal, null, 
     new SlidingTime(TimeSpan.FromSeconds(5))); 
} 


更多信息請參見The Definitive Guide To Microsoft Enterprise Library

+0

,再加上一些解釋。我將你的答案標記爲接受的答案,盧克。我也爲這兩者+1了。 – AlanR 2009-08-20 11:33:35

3

原因之一是.Contains可以回來爲true.GetData可以返回null.GetData穿過整個到期系統(它似乎只返回的數據是沒有過期)和.Contains不檢查看看它的內容是否過期。

{ 
    cache.Add("key", "value", CacheItemPriority.Normal, 
       null, new SlidingTime(TimeSpan.FromSeconds(5))); 
    System.Threading.Thread.Sleep(6000); 
    Console.WriteLine(cache.Contains("key"));  /// true 
    Console.WriteLine(cache.GetData("key") != null); /// false 
    Console.WriteLine(cache.Contains("key"));  /// false 
} 

我有另外一個問題是,我不知道高速緩存中是否包含用空的項的值或緩存只是沒有包含鍵的條目。我使用的解決方法是,如果.GetData返回null.Containstrue,那麼null有目的地存儲在緩存中並且沒有過期。

+0

我認爲cache.Contains()可能返回true,然後讓緩存中的數據到期,然後讓cache.GetData()返回null,因爲緩存項已經過期。空返回值並不意味着空值存儲在緩存中。當然,這是不太可能的邊緣情況,但我認爲這是可能的。 – 2010-06-17 07:39:58

+0

@Tuzo對。如果數據過期,GetData調用將正常返回null。如果數據未過期,您只能確定是否有意將空值存儲在緩存中。 – EliThompson 2010-06-19 05:10:41

1

有點舊,但今天我遇到了一個非常類似的問題,如果我從緩存中檢索到期的值(加上最多25秒),我會收到一個空值。然而,微軟已經發現了這種情況,並提出了一個修復方案here,我只需要弄清楚如何實現它。

0

我知道這個問題是相當古老的,但問題是,你使用Contains然後試圖檢索值。在調用時間爲ContainsGetData之間,該項目已過期並被刪除,因此GetData返回null。這被稱爲競賽條件。

解決的辦法很簡單(無需使用鎖),不要使用Contains

private static void CachingBlockTest() 
{ 
    while (true) 
    { 
     System.Threading.Thread.Sleep(2000); 

     var key = new Random().Next(3).ToString(); 
     string value = cache.GetData(key) as string; 

     if (value == null) 
     { 
      value = key; 
      cache.Add(key, value, CacheItemPriority.Normal, new RefreshAction(), 
       new SlidingTime(TimeSpan.FromSeconds(5))); 
     } 
     Console.WriteLine("{0} --> '{1}'", key, value); 
    } 
} 
private class RefreshAction : ICacheItemRefreshAction 
{ 
    public void Refresh(string removedKey, object expiredValue, CacheItemRemovedReason removalReason) 
    { 
     Console.WriteLine("{0} --> {1} removed", removedKey, expiredValue); 
    } 
} 
總之