简体   繁体   English

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

[英]Microsoft Enterprise Library Caching Application Block not thread safe?

I created a super simple console app to test out the Enterprise Library Caching Application Block, and the behavior is baffling. 我创建了一个超级简单的控制台应用程序来测试企业库缓存应用程序块,行为令人困惑。 I'm hoping I screwed something that's easy to fix in the setup. 我希望我搞砸了一些容易在设置中修复的东西。 I have each item expire after 5 seconds for testing purposes. 为了测试目的,我让每个项目在5秒后过期。

Basic setup -- "Every second pick a number between 0 and 2. If the cache doesn't already have it, put it in there -- otherwise just grab it from the cache. Do this inside a LOCK statement to ensure thread safety. 基本设置 - “每秒选择0到2之间的数字。如果缓存还没有它,请将其放在那里 - 否则只需从缓存中获取它。在LOCK语句中执行此操作以确保线程安全。

APP.CONFIG: 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#: 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 -- How can I prevent the cache from returning nulls? 输出 - 如何防止缓存返回空值?

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

What you are seeing is that your CacheItem has expired due to the 5 second SlidingTime expiration. 您看到的是由于5秒SlidingTime到期,您的CacheItem已过期。

Before returning the cached value, the GetData method performs a check to see if the CacheItem has expired. 在返回缓存值之前,GetData方法执行检查以查看CacheItem是否已过期。 If it has expired, the CacheItem is removed from the cache and null is returned. 如果它已过期,则从缓存中删除CacheItem并返回null。 However, the call to Contains will return true because the CacheItem is in the cache even though it's expiration may have elapsed. 但是,对Contains的调用将返回true,因为CacheItem位于缓存中,即使它已过期也可能已过期。 This seems to be by design. 这似乎是设计上的。 With that in mind, it would be wise not to cache a null value to represent no data since you would not be able to discern an expired CacheItem from an actual cached value of null. 考虑到这一点,最好不要缓存空值来表示没有数据,因为您无法从实际缓存的null值中识别过期的CacheItem。

Assuming that you do not cache a null value then Luke's solution should suit you: 假设您没有缓存空值,那么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)));
}


See The Definitive Guide To Microsoft Enterprise Library for more information. 有关详细信息,请参阅Microsoft企业库的权威指南

I notice that you seem to be getting null back from the cache whenever that item hasn't been accessed during the previous 5 loop iterations (ie, 5 seconds). 我注意到,在前5次循环迭代(即5秒)期间,如果没有访问该项,您似乎从缓存中返回null Could this be related to your 5 second expiry time? 这可能与你的5秒到期时间有关吗?

It seems unlikely, but maybe you have a race condition and the items are dropping out of the cache between the Contains check and the GetData retrieval. 这似乎不太可能,但也许你有一个竞争条件,并且这些项目正在从Contains检查和GetData检索之间的缓存中退出。

Try this change and see if it makes any difference to the output: 尝试此更改,看看它是否对输出有任何影响:

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);
}

One of the reasons that .Contains can come back as true and .GetData can return a null is that .GetData goes through the whole expiration system (it seems to only return data which isn't expired) and .Contains doesn't check to see if it's content is expired. 其中一个原因是.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
}

Another problem I had was that I couldn't tell whether the cache contained an entry with null as the value or the cache just didn't contain an entry for key. 我遇到的另一个问题是我无法判断缓存是否包含一个带有null值的条目,或者缓存只是没有包含密钥条目。 A workaround that I use is that if .GetData comes back with a null and .Contains is true , then a null was purposefully stored in the cache and is not expired. 我使用的一种解决方法是,如果.GetData返回null并且.Containstrue ,那么null有意地存储在缓存中并且没有过期。

Although this might not fix your particular problem, double-checked locking is usually suggested... 虽然这可能无法解决您的特定问题,但通常建议使用双重检查锁定...

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

Also possibly look into CacheItemRemovedCallback. 也可以查看CacheItemRemovedCallback。

A bit old, however I faced a very similar issue today in that if I retrieve a value from the cache that is due to expire (plus up to 25 extra seconds) I receive a null value. 有点旧,但是今天我遇到了一个非常类似的问题,如果我从缓存中检索一个到期的值(加上多达25秒),我会收到一个空值。 However Microsoft have acknwoledged this situation and suggested a fix here , I just have to figure out how to implement it. 然而,微软已经解决了这种情况,并建议在这里修复,我只需要弄清楚如何实现它。

I know this question is quite old, but the issue is that you're using Contains then attempting to retrieve the value. 我知道这个问题很老,但问题是你正在使用Contains然后尝试检索值。 In between the time Contains and GetData were called, the item expired and was removed, so GetData returned null. 在调用ContainsGetData的时间之间,项目已过期并被删除,因此GetData返回null。 This is known as a race condition. 这被称为竞争条件。

The solution is quite simple (without using locks), don't use Contains 解决方案非常简单(不使用锁),不要使用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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM