web-dev-qa-db-ja.com

MemoryCache AbsoluteExpirationの動作がおかしい

.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;
}
32
Jared

私はそれを理解しました。 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;
}
39
Jared

MatteoSpへ-構成のpollingIntervalまたはコンストラクターのNameValueCollectionは異なるタイマーです。呼び出されたときに、他の2つの構成プロパティを使用して、メモリがTrimメソッドを使用してエントリを削除する必要があるレベルにあるかどうかを判断する間隔です。

4
user487779

古い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.

タイマーに基づく有効期限がなくなりました(これはパフォーマンス設計上の決定です)。したがって、メモリプレッシャーまたはキャッシュされたアイテムのGet()ベースのメソッドの呼び出しが有効期限のトリガーになりました。ただし、キャンセルトークンを使用して時間ベースの有効期限を強制することができます。例については、このSO回答 https://stackoverflow.com/a/47949111/314085 を参照してください。

2
alastairtree

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