4 votes

Le bloc d'application de mise en cache de la bibliothèque Microsoft Enterprise n'est pas sûr pour les threads ?

J'ai créé une application console très simple pour tester le bloc d'application de mise en cache de la bibliothèque d'entreprise, et le comportement est déconcertant. J'espère que j'ai foiré quelque chose de facile à corriger dans l'installation. J'ai fait en sorte que chaque élément expire au bout de 5 secondes à des fins de test.

Configuration de base -- "Toutes les secondes, choisissez un nombre entre 0 et 2. Si le cache ne l'a pas déjà, mettez-le dedans -- sinon prenez-le dans le cache. Faites ceci à l'intérieur d'une instruction LOCK pour assurer la sécurité des threads.

APP.CONFIG :

<configuration>
  <configSections>
    <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <cachingConfiguration defaultCacheManager="Cache Manager">
    <cacheManagers>
      <add expirationPollFrequencyInSeconds="1" maximumElementsInCacheBeforeScavenging="1000"
      numberToRemoveWhenScavenging="10" backingStoreName="Null Storage"
      type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      name="Cache Manager" />
    </cacheManagers>
    <backingStores>
      <add encryptionProviderName="" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
      name="Null Storage" />
    </backingStores>
  </cachingConfiguration>
</configuration>

C# :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;

namespace ConsoleApplication1
{
    class Program
    {
        public static ICacheManager cache = CacheFactory.GetCacheManager("Cache Manager");
    static void Main(string[] args)
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1000); // sleep for one second.
                var key = new Random().Next(3).ToString();
                string value;
                lock (cache)
                {
                    if (!cache.Contains(key))
                    {
                        cache.Add(key, key, CacheItemPriority.Normal, null, new SlidingTime(TimeSpan.FromSeconds(5)));
                    }
                    value = (string)cache.GetData(key);
                }
                Console.WriteLine("{0} --> '{1}'", key, value);
                //if (null == value) throw new Exception(); 
            }
        }
    }
}

OUTPUT -- Comment empêcher le cache de renvoyer des nullités ?

2 --> '2'
1 --> '1'
2 --> '2'
0 --> '0'
2 --> '2'
0 --> '0'
1 --> ''
0 --> '0'
1 --> '1'
2 --> ''
0 --> '0'
2 --> '2'
0 --> '0'
1 --> ''
2 --> '2'
1 --> '1'
Press any key to continue . . .

10voto

Tuzo Points 14234

Ce que vous voyez, c'est que votre CacheItem a expiré en raison de l'expiration du SlidingTime de 5 secondes.

Avant de renvoyer la valeur mise en cache, la méthode GetData vérifie si le CacheItem a expiré. Si c'est le cas, l'élément de cache est supprimé du cache et null est renvoyé. Cependant, l'appel à Contains renverra true car le CacheItem est dans le cache même si son expiration a eu lieu. Cela semble avoir été conçu. En gardant cela à l'esprit, il serait sage de ne pas mettre en cache une valeur null pour représenter l'absence de données, car vous ne seriez pas en mesure de discerner un CacheItem expiré d'une valeur null mise en cache.

Si vous ne mettez pas en cache une valeur nulle, la solution de Luke devrait vous convenir :

value = cache.GetData(key) as string;

// If null was returned then it means that there was no item in the cache 
// or that there was an item in the cache but it had expired 
// (and was removed from the cache)
if (value == null)
{
    value = key;
    cache.Add(key, value, CacheItemPriority.Normal, null,
        new SlidingTime(TimeSpan.FromSeconds(5)));
}

Voir Le guide définitif de la bibliothèque Microsoft Enterprise pour plus d'informations.

7voto

LukeH Points 110965

J'ai remarqué que vous semblez obtenir null de la mémoire cache chaque fois que cet élément n'a pas été consulté au cours des 5 dernières itérations de la boucle (c'est-à-dire 5 secondes). Cela pourrait-il être lié à votre délai d'expiration de 5 secondes ?

Cela semble peu probable, mais il se peut que vous ayez une condition de course et que les éléments sortent de la mémoire cache entre le moment où l'option Contains et le GetData récupération.

Essayez cette modification et voyez si cela fait une différence au niveau de la sortie :

while (true)
{
    System.Threading.Thread.Sleep(1000);

    var key = new Random().Next(3).ToString();
    string value;

    lock (cache)
    {
        value = (string)cache.GetData(key);
        if (value == null)
        {
            value = key;
            cache.Add(key, value, CacheItemPriority.Normal, null,
                new SlidingTime(TimeSpan.FromSeconds(5)));
        }
    }
    Console.WriteLine("{0} --> '{1}'", key, value);
}

3voto

alephtwo Points 862

L'une des raisons pour lesquelles les .Contains peut revenir en tant que true y .GetData peut renvoyer un null est que .GetData passe par tout le système d'expiration (il semble ne renvoyer que les données qui n'ont pas expiré) et .Contains ne vérifie pas si son contenu est expiré.

{
    cache.Add("key", "value", CacheItemPriority.Normal, 
              null, new SlidingTime(TimeSpan.FromSeconds(5)));
    System.Threading.Thread.Sleep(6000);
    Console.WriteLine(cache.Contains("key"));        /// true
    Console.WriteLine(cache.GetData("key") != null); /// false
    Console.WriteLine(cache.Contains("key"));        /// false
}

Un autre problème que j'ai rencontré est que je ne pouvais pas savoir si le cache contenait une entrée avec null comme valeur ou si le cache ne contenait tout simplement pas d'entrée pour la clé. Une solution de contournement que j'utilise est que si .GetData revient avec un null y .Contains es true , alors a null a été volontairement stocké dans le cache et n'a pas expiré.

1voto

Axl Points 3077

Bien que cela puisse ne pas résoudre votre problème particulier, il est généralement conseillé de procéder à une double vérification du verrouillage...

if (!cache.Contains(key))
{
    lock(mylockobj)
    {
        if (!cache.Contains(key))
        {
             cache.Add(key, key)
        }
    }
}

Il est également possible d'examiner CacheItemRemovedCallback.

1voto

ImMe Points 21

C'est un peu vieux, mais j'ai été confronté à un problème très similaire aujourd'hui : si je récupère une valeur du cache qui doit expirer (plus 25 secondes supplémentaires), je reçois une valeur nulle. Cependant, Microsoft a reconnu cette situation et a suggéré un correctif. aquí Il ne me reste plus qu'à trouver comment le mettre en œuvre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X