我正在尝试使用.net 4.5中的MemoryCache
来跟踪并自动更新各种项目,但似乎无论我设置为AbsoluteExpiration
它始终只会过期在15秒或更长时间内。
我希望缓存项目每5秒过期一次,但它总是在至少15秒内到期,如果我将过期时间移出,它最终将达到15秒+我的刷新间隔,但绝不会少于超过15秒。
是否有一些我没有看到的内部定时器分辨率?我查看了一些反映的System.Runtime.Caching.MemoryCache
代码,没有任何突出的东西对我来说,我无法在互联网上找到任何有这个问题的人。
我在下面有一个非常基本的例子来说明问题。
我想要的是CacheEntryUpdate
每5秒左右就会被点击一次并使用新数据进行更新,但正如我所说,它只会在15秒内被击中。
static MemoryCache MemCache;
static int RefreshInterval = 5000;
protected void Page_Load(object sender, EventArgs e)
{
if (MemCache == null)
MemCache = new MemoryCache("MemCache");
if (!MemCache.Contains("cacheItem"))
{
var cacheObj = new object();
var policy = new CacheItemPolicy
{
UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)
};
var cacheItem = new CacheItem("cacheItem", cacheObj);
MemCache.Set("cacheItem", cacheItem, policy);
}
}
private void CacheEntryUpdate(CacheEntryUpdateArguments args)
{
var cacheItem = MemCache.GetCacheItem(args.Key);
var cacheObj = cacheItem.Value;
cacheItem.Value = cacheObj;
args.UpdatedCacheItem = cacheItem;
var policy = new CacheItemPolicy
{
UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)
};
args.UpdatedCacheItemPolicy = policy;
}
答案 0 :(得分:39)
我已经弄清楚了。 System.Runtime.Caching.CacheExpires上有一个名为_tsPerBucket的internal static readonly TimeSpan
,它在20秒内被硬编码。
显然,此字段用于运行的内部计时器,并检查缓存项是否已过期。
我正在解决这个问题,方法是使用反射覆盖值并清除默认的MemoryCache实例以重置所有内容。它似乎有效,即使它是一个巨大的黑客。
这是更新后的代码:
static MemoryCache MemCache;
static int RefreshInterval = 1000;
protected void Page_Load(object sender, EventArgs e)
{
if (MemCache == null)
{
const string assembly = "System.Runtime.Caching, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
var type = Type.GetType("System.Runtime.Caching.CacheExpires, " + assembly, true, true);
var field = type.GetField("_tsPerBucket", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, TimeSpan.FromSeconds(1));
type = typeof(MemoryCache);
field = type.GetField("s_defaultCache", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, null);
MemCache = new MemoryCache("MemCache");
}
if (!MemCache.Contains("cacheItem"))
{
var cacheObj = new object();
var policy = new CacheItemPolicy
{
UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)
};
var cacheItem = new CacheItem("cacheItem", cacheObj);
MemCache.Set("cacheItem", cacheItem, policy);
}
}
private void CacheEntryUpdate(CacheEntryUpdateArguments args)
{
var cacheItem = MemCache.GetCacheItem(args.Key);
var cacheObj = cacheItem.Value;
cacheItem.Value = cacheObj;
args.UpdatedCacheItem = cacheItem;
var policy = new CacheItemPolicy
{
UpdateCallback = new CacheEntryUpdateCallback(CacheEntryUpdate),
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(RefreshInterval)
};
args.UpdatedCacheItemPolicy = policy;
}
答案 1 :(得分:4)
到MatteoSp - 配置中的pollingInterval或构造函数中的NameValueCollection是一个不同的计时器。调用时将使用另外两个配置属性来确定内存是否处于需要使用Trim方法删除条目的级别。
答案 2 :(得分:2)
您是否愿意/能够从旧的System.Runtime.Caching更改为新的Microsft.Extensions.Caching?版本1.x支持netstandard 1.3和net451。如果是这样,那么改进的API将支持您描述的用法而不会带有反射的烦恼。
MemoryCacheOptions对象有一个属性ExpirationScanFrequency,允许您控制缓存清理的扫描频率,请参阅https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions.expirationscanfrequency?view=aspnetcore-2.0
请注意,基于计时器不再有过期(这是性能设计决策),因此现在内存压力或为缓存项调用其中一个基于Get()的方法现在是到期的触发器。但是,您可以使用取消令牌强制基于时间的到期,请参阅此SO答案以获取示例https://stackoverflow.com/a/47949111/3140853。
答案 3 :(得分:0)
基于@Jared的答案的更新版本。修改默认的MemoryCache实例,在这里创建一个新的。
class FastExpiringCache
{
public static MemoryCache Default { get; } = Create();
private static MemoryCache Create()
{
MemoryCache instance = null;
Assembly assembly = typeof(CacheItemPolicy).Assembly;
Type type = assembly.GetType("System.Runtime.Caching.CacheExpires");
if( type != null)
{
FieldInfo field = type.GetField("_tsPerBucket", BindingFlags.Static | BindingFlags.NonPublic);
if(field != null && field.FieldType == typeof(TimeSpan))
{
TimeSpan originalValue = (TimeSpan)field.GetValue(null);
field.SetValue(null, TimeSpan.FromSeconds(3));
instance = new MemoryCache("FastExpiringCache");
field.SetValue(null, originalValue); // reset to original value
}
}
return instance ?? new MemoryCache("FastExpiringCache");
}
}