C# MemoryCache AbsoluteExpiration 行为奇怪

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12630168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 23:57:12  来源:igfitidea点击:

MemoryCache AbsoluteExpiration acting strange

c#.netmemorycache

提问by Jared

I'm trying to use a MemoryCachein .net 4.5 to keep track of and automatically update various items, but it seems like no matter what I set as an AbsoluteExpirationit will always only expire in 15 seconds or more.

我正在尝试使用MemoryCache.net 4.5 中的 a 来跟踪和自动更新各种项目,但似乎无论我设置为什么,AbsoluteExpiration它始终只会在 15 秒或更长时间内过期。

I want the cache items to expire every 5 seconds, but it always expires in at least 15 seconds, and if I move the expiration time out, it will end up being something like 15 seconds + my refresh interval, but never less than 15 seconds.

我希望缓存项每 5 秒过期一次,但它总是在至少 15 秒后过期,如果我将过期时间移出,它最终将是 15 秒 + 我的刷新间隔,但绝不会少于 15 秒.

Is there some internal timer resolution that I'm not seeing? I looked through a bit of the reflected System.Runtime.Caching.MemoryCachecode and nothing stood out to me, and I haven't been able to find anybody else who has this issue out on the internet.

是否有一些我没有看到的内部计时器分辨率?我查看了一些反映的System.Runtime.Caching.MemoryCache代码,没有什么对我来说很突出,而且我无法在互联网上找到其他有此问题的人。

I have a very basic example below that illustrates the problem.

我在下面有一个非常基本的例子来说明这个问题。

What I want is for CacheEntryUpdateto be hit every 5 seconds or so and update with new data, but, as I've said, it only ever gets hit in 15+ seconds.

我想要的是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;
}

采纳答案by Jared

I've figured it out. There's an internal static readonly TimeSpanon System.Runtime.Caching.CacheExpires called _tsPerBucket that is hardcoded at 20 seconds.

我已经想通了。internal static readonly TimeSpanSystem.Runtime.Caching.CacheExpires 上有一个名为 _tsPerBucket 的硬编码在 20 秒。

Apparently, this field is what's used on the internal timers that run and check to see if cache items are expired.

显然,此字段用于运行并检查缓存项是否已过期的内部计时器。

I'm working around this by overwriting the value using reflection and clearing the default MemoryCache instance to reset everything. It seems to work, even if it is a giant hack.

我正在通过使用反射覆盖值并清除默认 MemoryCache 实例来重置所有内容来解决此问题。它似乎有效,即使它是一个巨大的黑客。

Here's the updated code:

这是更新后的代码:

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

回答by user487779

To MatteoSp - the pollingInterval in the configuration or the NameValueCollection in the constructor is a different timer. It is an interval that when called will use the two other config properties to determine if memory is at a level that requires entries to be removed using the Trim method.

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

回答by Mr.Wang from Next Door

An updated version basing on @Jared's answer. Insread of modify the default MemoryCache instance, here creates a new one.

基于@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");
    }
}

回答by alastairtree

Would you be willing/able to change from the older System.Runtime.Caching to the new Microsft.Extensions.Caching? version 1.x supports netstandard 1.3 and net451. If so then the improved API would support the usage you describe without hackery with reflection.

您愿意/能够从旧的 System.Runtime.Caching 更改为新的Microsft.Extensions.Caching吗?1.x 版支持 netstandard 1.3 和 net451。如果是这样,那么改进的 API 将支持您描述的用法,而无需通过反射进行黑客攻击。

The MemoryCacheOptions object has a property ExpirationScanFrequency to allow you to control the scan frequency of the cache cleanup, see https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions.expirationscanfrequency?view=aspnetcore-2.0

MemoryCacheOptions 对象有一个属性 ExpirationScanFrequency 允许您控制缓存清理的扫描频率,请参阅https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycacheoptions.expirationscanfrequency ?view=aspnetcore-2.0

Be aware that there is no longer expiration based on timers (this is a performance design decision), and so now memory pressure or calling one of the Get() based methods for the cached items are now the triggers for expiration. However you can force time based expiration using cancellation tokens, see this SO answer for an example https://stackoverflow.com/a/47949111/3140853.

请注意,不再有基于计时器的过期(这是性能设计决策),因此现在内存压力或为缓存项调用基于 Get() 的方法之一现在是过期的触发器。但是,您可以使用取消令牌强制基于时间的到期,请参阅此 SO 答案以获取示例https://stackoverflow.com/a/47949111/3140853