MemoryCache AbsoluteExpiration действует странно

Я пытаюсь использовать MemoryCache в .net 4.5, чтобы отслеживать и автоматически обновлять различные элементы, но похоже, что независимо от того, что я установил как 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 ответов


Я все понял. Есть internal static readonly TimeSpan о системе.Во время выполнения.Кэширование.CacheExpires называется _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;
}

to MatteoSp-pollingInterval в конфигурации или NameValueCollection в конструкторе является другим таймером. Это интервал, который при вызове будет использовать два других свойства конфигурации, чтобы определить, находится ли память на уровне, требующем удаления записей с помощью метода Trim.


хотели бы вы / могли бы изменить старую систему.Во время выполнения.Кэширование к новому узел Майкрософт.Увеличение.Кэширование? Версия 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.


обновленная версия на основе ответа @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");
    }
}