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

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

基本设置 – “每秒选择0到2之间的数字。如果缓存还没有,请将其放在那里 – 否则只需从缓存中获取它。在LOCK语句中执行此操作以确保线程安全。

App.config中:

  

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

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

您所看到的是由于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))); } 

有关详细信息,请参阅Microsoft企业库的权威指南 。

其中一个原因是.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有意地存储在缓存中并且未过期。

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

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

也可以查看CacheItemRemovedCallback。

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

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