【问题标题】:Microsoft Enterprise Library Caching Application Block not thread safe?Microsoft 企业库缓存应用程序块不是线程安全的?
【发布时间】:2010-11-21 02:51:27
【问题描述】:

我创建了一个超级简单的控制台应用程序来测试企业库缓存应用程序块,其行为令人费解。我希望我搞砸了一些容易在设置中修复的东西。出于测试目的,我让每个项目在 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 . . .

【问题讨论】:

  • 总之,Caching Application Block“以线程安全的方式执行”。
  • 看来,如果项目过期, Contains() 只会返回 true。但是 GetData() 在这种情况下返回 null。如果将该条件更改为 if ((value = GetData(...)) == null) 一切正常

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


【解决方案1】:

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

在返回缓存值之前,GetData 方法会检查 CacheItem 是否已过期。如果已过期,则从缓存中删除 CacheItem 并返回 null。但是,对 Contains 的调用将返回 true,因为 CacheItem 在缓存中,即使它的过期时间可能已经过去。这似乎是设计使然。考虑到这一点,明智的做法是不要缓存空值来表示没有数据,因为您将无法从实际缓存的空值中辨别出过期的 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。
【解决方案2】:

我注意到,只要在前 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);
}

【讨论】:

  • 我理解人们为什么会这样做,但老实说,你会认为缓存足够简洁,不会让你经历这些废话。老实说,我只想执行“GetData”并提供 RefreshAction 并完成它
【解决方案3】:

.Contains 可以返回 true.GetData 可以返回 null 的原因之一是 .GetData 经历了整个过期系统(它似乎只返回不是expired) 并且.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 被故意存储在缓存中并且不会过期。

【讨论】:

  • 我认为cache.Contains() 有可能返回true,然后让缓存中的数据过期,然后让cache.GetData() 返回null,因为缓存项已过期。 null 返回值并不意味着 null 值存储在缓存中。当然,这是一个不太可能的极端情况,但我认为这是可能的。
  • @Tuzo 对。如果数据过期,对它的 GetData 调用将正常返回 null。如果数据没有过期,您只能判断 null 是否故意存储在缓存中。
【解决方案4】:

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

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

也可能查看 CacheItemRemovedCallback。

【讨论】:

  • 双重检查锁定是一个坏习惯,不推荐,因为它不起作用
【解决方案5】:

有点老了,但是我今天遇到了一个非常相似的问题,如果我从缓存中检索一个即将过期的值(加上最多 25 秒的额外时间),我会收到一个空值。但是微软已经承认了这种情况并提出了修复here,我只需要弄清楚如何实现它。

【讨论】:

    【解决方案6】:

    我知道这个问题已经很老了,但问题是您正在使用 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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-02-21
      • 1970-01-01
      • 2011-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多