Подтвердить что ты не робот

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

Ответ 1

Я понял это. Там internal static readonly TimeSpan на System.Runtime.Caching.CacheExpires называется _tsPerBucket, который жестко запрограммирован через 20 секунд.

По-видимому, это поле используется во внутренних таймерах, которые запускаются, и проверяет, не исчезли ли элементы кэша.

Я работаю над этим, перезаписав значение с помощью отражения и очистив экземпляр MemoryCache по умолчанию до reset. Кажется, что это работает, даже если это гигантский взлом.

Здесь обновленный код:

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

Ответ 2

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

Ответ 3

Готовы ли вы/могли бы перейти от старого 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? вид = aspnetcore-2,0

Имейте в виду, что больше нет истечения срока действия на основе таймеров (это решение для оценки производительности), и поэтому теперь давление памяти или вызов одного из методов Get() для кэшированных элементов теперь являются триггерами для истечения срока действия. Однако вы можете принудительно завершить время с использованием токенов отмены, см. Этот ответ SO для примера fooobar.com/questions/12299183/....

Ответ 4

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