2009-08-19 48 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); 
    } 
} 
总之
相关问题