Comment traiter avec les coûteuses opérations de renforcement à l'aide de MemoryCache?
Sur un ASP.NET projet MVC nous avons plusieurs cas de données qui nécessite une bonne quantité de temps et de ressources pour construire. Nous voulons les mettre en cache.
MemoryCache
fournit un certain niveau de fil de sécurité, mais pas assez pour éviter l'exécution de plusieurs instances de code du bâtiment en parallèle. Voici un exemple:
var data = cache["key"];
if(data == null)
{
data = buildDataUsingGoodAmountOfResources();
cache["key"] = data;
}
Comme vous pouvez le voir sur un site occupé de centaines de threads peuvent aller à l'intérieur de la si la déclaration simultanément jusqu'à ce que les données sont construites et de faire de l'exploitation d'un bâtiment encore plus lente, consommer inutilement les ressources du serveur.
Il y a un atomique AddOrGetExisting
mise en œuvre dans MemoryCache mais il requiert incorrectement "valeur pour définir" au lieu de "code pour récupérer la valeur à définir" qui, je pense, rend la méthode donnée presque totalement inutile.
Nous avons été à l'aide de notre propre ad-hoc de l'échafaudage autour de MemoryCache pour y arriver mais il faut explicite lock
s. C'est gênant à l'utilisation par l'entrée de verrouillage des objets et nous avons l'habitude de sortir par le partage d'objets de verrou qui est loin d'être idéale. Qui m'a fait penser que les raisons d'éviter une telle convention pourrait être intentionnelle.
J'ai donc deux questions:
-
Est-il préférable de ne pas
lock
code du bâtiment? (Qui peut avoir été prouvé plus réactifs pour l'un, je me demande) -
Quel est le bon chemin pour atteindre par l'entrée de verrouillage pour MemoryCache pour une telle serrure? La forte envie d'utiliser
key
chaîne que le verrou de l'objet est rejeté à l' ".NET de blocage 101".
Vous devez vous connecter pour publier un commentaire.
Nous avons résolu ce problème en combinant des
Lazy<T>
avecAddOrGetExisting
pour éviter un besoin pour un verrouillage de l'objet complètement. Voici un exemple de code (qui utilise infini d'expiration):Ce n'est pas complète. Il y a des erreurs comme "exception" mise en cache de sorte que vous avez à décider sur ce que vous voulez faire dans le cas où votre valueFactory throws exception. L'un des avantages, cependant, est la possibilité de mettre en cache les valeurs null trop.
newValue
n'est jamais réellement ajoutée au cache une fois évaluées? Depuisvalue
estnull
sur la 1ère passe, car il n'y a rien dans le cache, et à l'aide deLazy<T>
signifie l'expression n'est pas évaluée jusqu'à ce que la ligne suivante:return (value ?? newValue).Value;
quand ou Comment nenewValue
ont une chance d'être ajouté à la cache? Ce que je constate c'est que le cache est jamais renseigné sur chaque appel, parce quenewValue
n'est jamais ajouté.newValue.Value
est nécessaire donc de l'évaluation de lavalueFactory
expression sur chaque appel. Par le moyen de votre code doit être modifié un peu pour moi. Votre dernière ligne de mon code devait être la suivante:return (value ?? newValue.Value);
newValue.Value
est appelé (carvalue
sera nulle) en le peuplant. sinonvalue.Value
est appelé, ce qui renvoie la valeur existante. ce qui est mal à cela? valeur de retour va donner une erreur de compilation car les deux ont le type deLazy<T>
. vous devez retourner.Value
pour les deux options.T
n'était pas de la compilation, donc je les ai remplacés par le type de béton (dans mon casIEnumerable<MyCustomCollection>
qui est stockée dans la mémoire cache ou de l'extrait devalueFactory
expression. Quand vous faites cela, je n'ai pas euas Lazy<IEnumerable<MyCustomCollection>>
sur la fin de la ligne qui appellecache.AddOrGetExisting
. Une fois que j'avais tous les types définis de la même le code d'origine a travaillé (à l'exception des génériques)T
ne serait pas du travail pour moi? J'ai continué à obtenirCannot resolve symbol 'T'
. J'ai utiliser des génériques dans mon référentiel souvent, mais dans la comparaison, je ne pouvais pas comprendre pourquoi ça ne marcherait pas. Puisque c'était une mise en œuvre spécifique, en le remplaçant par types de béton fonctionne bien pour l'instant.public T GetFromCache<T>(string key, Func<T> valueFactory)
as Lazy<T>
. Sinon, joli!LazyThreadSafetyMode.PublicationOnly
dans leLazy<T>
constructeur à éviter la mise en cache des exceptions (si désiré).LazyThreadSafetyMode.PublicationOnly
. Avec ce mode, plusieurs threads peuvent commencer l'exécution de la méthode d'initialisation dans le même temps (avec le gagnant de la valeur utilisée). Si le coût de cette méthode est élevé (d'où la mise en cache), ce n'est probablement pas souhaité. La seule façon d'obtenir de l'initialisation de l'exclusivité est d'utiliser le mode par défaut deExecutionAndPublication
. La prévention exception de la mise en cache devront être traitées manuellement.Pour le conditionnel ajouter exigence, j'ai toujours utiliser
ConcurrentDictionary
, qui a une surchargeGetOrAdd
méthode qui accepte un délégué à feu si l'objet doit être construit.En réalité, j'ai presque toujours utiliser
static
simultanées dictionnaires. J'ai utilisé "normal" dictionnaires protégé par unReaderWriterLockSlim
exemple, mais dès que je suis passé à .Net 4 (il est uniquement disponible à partir de ce partir) j'ai commencé la conversion de l'un de ceux que j'ai croisé.ConcurrentDictionary
's de performance est admirable pour dire le moins 🙂Mise à jour Naïf de mise en œuvre de l'expiration de la sémantique fondée sur l'âge uniquement. Doivent également veiller à ce que les éléments individuels sont créés uniquement pour une fois - comme par @usr suggestion. Mise à jour - comme @usr a suggéré simplement à l'aide d'un
Lazy<T>
serait beaucoup plus simple - vous pouvez juste avant la création de délégué pour que lors de l'ajout à la concurrente de dictionnaire. Je be changé le code, comme le fait mon dictionnaire de serrures n'aurait pas marché de toute façon. Mais faut vraiment que j'y ai pensé moi-même (minuit passé ici, dans le royaume-UNI et si je suis battu. Toute la sympathie? Sans évidemment pas. Étant développeur, j'ai assez de caféine qui coule dans mes veines à réveiller les morts).Je ne le recommande la mise en œuvre de la
IRegisteredObject
interface avec cela, cependant, puis l'enregistrer avec leHostingEnvironment.RegisterObject
méthode - faire, qui constituerait un moyen le plus propre pour arrêter le poller fil lorsque le pool d'applications s'éteint/recycle.ObjectCache
de base abstraite quiMemoryCache
utilise. L'une des choses cool sur ConcurrentDictionary est que ce sont des multiples lecteur, un seul écrivain. Un bureau de vote thread peut numériser en même temps qu'un autre extrait. Une fois la liste des éléments à expiré est compilé, ils peuvent tous être fait, à son tour, et les lecteurs d'attendre automatiquement.new object()
instances d'agir comme atomique de verrous pour le code de l'utilisateur serait une bonne idée? Ils peuvent être ajoutés à l'aide deTryAdd
. Il faut dire, d'ailleurs - que j'ai trouvé dans la pratique, que même avec un immenses montant de la charge (enregistrer en cours d'exécution beaucoup de threads dérouillée le dictionnaire), de tels chevauchements se produisent rarement.factory(k)
paramètres de sorte qu'il sera effectivement exécutée deux fois....droit?CD<TKey, Lazy<TKValue>>
modèle est standard et de sécurité.key A
, il sera exécuté , mais si un utilisateur demandekey B
, la fonction run nouveau ?cache["key"] = LongRunningOperation()
. mais dans ce, le temps ne le cache commence la connaissance et de l'armement "key
" ? est-il au début deLongRunningOperation
ou à la fin de celui-ci ?Voici un design qui suit ce que vous semblez avoir à l'esprit. Le premier verrou ne se produit que pour un court laps de temps. Le dernier appel de données.La valeur des écluses (en-dessous), mais les clients ne bloquera que si deux d'entre eux demandent le même élément en même temps.
Prenant la tête de réponse en C# 7, voici mon application qui permet le stockage de tout type de source de
T
à tout type de retourTResult
.D'utilisation
Sedat la solution de la combinaison de Paresseux avec AddOrGetExisting est source d'inspiration. Je dois souligner que cette solution a un problème de performance, ce qui semble très important pour une solution de mise en cache.
Si vous regardez la code de AddOrGetExisting(), vous trouverez que AddOrGetExisting() n'est pas un lock-libre de méthode. En comparant le lock-free méthode Get (), il gaspille l'un des avantage de MemoryCache.
Je voudrais vous recommandons de suivre la solution, en utilisant Get() en premier et ensuite utiliser AddOrGetExisting() pour éviter la création d'objet à plusieurs reprises.
Ici est la solution la plus simple que MemoryCache méthode d'extension.
Et de test pour elle comme une description de l'utilisation.