Microsoft企业库缓存应用程序块不是线程安全的?

时间:2009-08-19 21:08:34

标签: c# .net caching thread-safety enterprise-library

我创建了一个超级简单的控制台应用程序来测试企业库缓存应用程序块,行为令人困惑。我希望我搞砸了一些容易在设置中修复的东西。我让每个项目在5秒后过期以进行测试。

基本设置 - “每秒选择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(); 
            }
        }
    }
}

OUTPUT - 如何防止缓存返回空值?

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

6 个答案:

答案 0 :(得分:10)

您看到的是由于5秒SlidingTime到期,您的CacheItem已过期。

在返回缓存值之前,GetData方法会检查CacheItem是否已过期。如果它已过期,则从缓存中删除CacheItem并返回null。但是,对Contains的调用将返回true,因为CacheItem位于缓存中,即使它已过期也可能已过期。这似乎是设计上的。考虑到这一点,最好不要缓存空值来表示没有数据,因为您无法从实际缓存的null值中识别过期的CacheItem。

假设您没有缓存空值,那么Luke的解决方案应该适合您:

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

答案 1 :(得分: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);
}

答案 2 :(得分: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
}

我遇到的另一个问题是我无法判断缓存中是否包含一个带有null值的条目,或者缓存只是没有包含密钥条目。我使用的解决方法是,如果.GetData返回null.Containstrue,则null有意存储在缓存中,没过期。

答案 3 :(得分:1)

虽然这可能无法解决您的特定问题,但通常建议使用双重检查锁定...

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

也可以查看CacheItemRemovedCallback。

答案 4 :(得分:1)

有点老了,但是今天我遇到了一个非常类似的问题,如果我从缓存中检索一个到期的值(加上多达25秒),我会收到一个空值。然而,微软已经解决了这种情况,并建议修复here,我只需要弄清楚如何实现它。

答案 5 :(得分: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);
    }
}