web-dev-qa-db-fra.com

MemoryCache AbsoluteExpiration agissant étrange

J'essaie d'utiliser MemoryCache dans .net 4.5 pour garder trace et mettre à jour automatiquement divers éléments, mais il semble que peu importe ce que j'ai défini comme AbsoluteExpiration, il n'expire toujours que dans 15 secondes ou plus.

Je souhaite que les éléments de la mémoire cache expirent toutes les 5 secondes, mais il expire toujours au moins 15 secondes. Si je déplace le délai d'expiration, le délai d'expiration est d'environ 15 secondes + mon intervalle d'actualisation, mais jamais moins de 15 secondes. .

Existe-t-il une résolution de minuterie interne que je ne vois pas? J'ai parcouru un peu le code System.Runtime.Caching.MemoryCache reflété et rien ne m'a marqué, et je n'ai pas été en mesure de trouver quelqu'un d'autre qui a ce problème sur Internet.

J'ai un exemple très basique ci-dessous qui illustre le problème.

Ce que je veux, c’est que CacheEntryUpdate soit touché toutes les 5 secondes environ et mis à jour avec de nouvelles données, mais, comme je l’ai dit plus tôt, cela ne se produit qu’en 15 secondes ou plus.

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

Je l'ai compris. Il y a un internal static readonly TimeSpan sur System.Runtime.Caching.CacheExpires appelé _tsPerBucket, codé en dur à 20 secondes. 

Apparemment, ce champ correspond à ce qui est utilisé sur les minuteries internes qui s'exécutent et vérifie si les éléments de cache ont expiré.

Je travaille autour de cela en écrasant la valeur en utilisant la réflexion et en effaçant l'instance par défaut de MemoryCache pour tout réinitialiser. Cela semble fonctionner, même si c'est un hack géant.

Voici le code mis à jour:

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

To MatteoSp - pollingInterval dans la configuration ou NameValueCollection dans le constructeur est une minuterie différente. C'est un intervalle qui, lorsqu'il est appelé, utilisera les deux autres propriétés de configuration pour déterminer si la mémoire est à un niveau nécessitant la suppression d'entrées à l'aide de la méthode Trim.

4
user487779

Seriez-vous prêt/capable de passer de l'ancien System.Runtime.Caching au nouveau Microsft.Extensions.Caching ? la version 1.x supporte netstandard 1.3 et net451. Si tel est le cas, l'API améliorée prend en charge l'utilisation que vous décrivez sans piratage avec réflexion.

L'objet MemoryCacheOptions a la propriété ExpirationScanFrequency pour vous permettre de contrôler la fréquence d'analyse du nettoyage du cache, voir https://docs.Microsoft.com/en-us/dotnet/api/Microsoft.extensions.caching.memory.memorycacheoptions. .expirationscanfrequency? view = aspnetcore-2.0

Sachez qu'il n'y a plus d'expiration basée sur les minuteries (il s'agit d'une décision de conception de performances), de sorte que maintenant, la pression de la mémoire ou l'appel d'une des méthodes basées sur Get () pour les éléments mis en cache sont maintenant les déclencheurs de l'expiration. Cependant, vous pouvez forcer l'expiration basée sur le temps en utilisant des jetons d'annulation, voir cette réponse SO pour un exemple https://stackoverflow.com/a/47949111/3140853 .

2
alastairtree

Une version mise à jour basée sur la réponse de @ Jared. Si vous ne modifiez pas l'instance par défaut de MemoryCache, créez-en une nouvelle.

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