41

我正在尝试使用MemoryCache.net 4.5 中的 a 来跟踪并自动更新各种项目,但似乎无论我设置什么,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;
}
4

4 回答 4

48

我已经想通了。在 System.Runtime.Caching.CacheExpires 上有一个internal static readonly TimeSpan名为 _tsPerBucket 的硬编码为 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;
}
于 2012-09-28T18:27:08.397 回答
9

您愿意/能够从旧的 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

于 2018-02-27T11:26:03.883 回答
4

对于 MatteoSp - 配置中的 pollingInterval 或构造函数中的 NameValueCollection 是不同的计时器。这是一个间隔,调用时将使用其他两个配置属性来确定内存是否处于需要使用 Trim 方法删除条目的级别。

于 2014-02-17T19:19:05.077 回答
1

基于@Jared 回答的更新版本。Insread 修改默认的 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");
    }
}
于 2017-04-25T08:20:55.373 回答