Lassen Sie mich zunächst einmal wissen, dass der folgende Code nicht threadsicher ist (Korrektur: möglicherweise). Ich habe Probleme damit, eine Implementierung zu finden, die tatsächlich getestet werden kann. Ich überarbeite gerade ein großes WCF-Projekt, für das einige (meistens) statische Daten zwischengespeichert und aus einer SQL-Datenbank ausgefüllt werden müssen. Es muss mindestens einmal am Tag ablaufen und "aktualisiert" werden, weshalb ich MemoryCache verwende.
Ich weiß, dass der folgende Code nicht threadsicher sein sollte, aber ich kann nicht erreichen, dass er unter hoher Last fehlschlägt und um die Sache zu komplizieren. Eine Google-Suche zeigt Implementierungen in beide Richtungen (mit und ohne Sperren kombiniert mit Debatten, ob sie notwendig sind oder nicht).
Könnte jemand mit Kenntnissen über MemoryCache in einer Umgebung mit mehreren Threads mich definitiv wissen lassen, ob ich gegebenenfalls sperren muss, damit ein zu entfernender Aufruf (der selten aufgerufen wird, aber erforderlich ist) beim Abrufen / erneuten Auffüllen nicht ausgelöst wird.
public class MemoryCacheService : IMemoryCacheService
{
private const string PunctuationMapCacheKey = "punctuationMaps";
private static readonly ObjectCache Cache;
private readonly IAdoNet _adoNet;
static MemoryCacheService()
{
Cache = MemoryCache.Default;
}
public MemoryCacheService(IAdoNet adoNet)
{
_adoNet = adoNet;
}
public void ClearPunctuationMaps()
{
Cache.Remove(PunctuationMapCacheKey);
}
public IEnumerable GetPunctuationMaps()
{
if (Cache.Contains(PunctuationMapCacheKey))
{
return (IEnumerable) Cache.Get(PunctuationMapCacheKey);
}
var punctuationMaps = GetPunctuationMappings();
if (punctuationMaps == null)
{
throw new ApplicationException("Unable to retrieve punctuation mappings from the database.");
}
if (punctuationMaps.Cast<IPunctuationMapDto>().Any(p => p.UntaggedValue == null || p.TaggedValue == null))
{
throw new ApplicationException("Null values detected in Untagged or Tagged punctuation mappings.");
}
// Store data in the cache
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTime.Now.AddDays(1.0)
};
Cache.AddOrGetExisting(PunctuationMapCacheKey, punctuationMaps, cacheItemPolicy);
return punctuationMaps;
}
//Go oldschool ADO.NET to break the dependency on the entity framework and need to inject the database handler to populate cache
private IEnumerable GetPunctuationMappings()
{
var table = _adoNet.ExecuteSelectCommand("SELECT [id], [TaggedValue],[UntaggedValue] FROM [dbo].[PunctuationMapper]", CommandType.Text);
if (table != null && table.Rows.Count != 0)
{
return AutoMapper.Mapper.DynamicMap<IDataReader, IEnumerable<PunctuationMapDto>>(table.CreateDataReader());
}
return null;
}
}
quelle
Antworten:
Die standardmäßige MS-Bereitstellung
MemoryCache
ist vollständig threadsicher. Eine benutzerdefinierte Implementierung, von der abgeleitet wird, istMemoryCache
möglicherweise nicht threadsicher. Wenn Sie "MemoryCache
out of the box" verwenden, ist es threadsicher. Durchsuchen Sie den Quellcode meiner Open Source Distributed Caching-Lösung, um zu sehen, wie ich sie verwende (MemCache.cs):https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs
quelle
GetOrCreate
Methode. Es gibt ein Problem auf GithubWährend MemoryCache in der Tat threadsicher ist, wie andere Antworten angegeben haben, gibt es ein häufiges Problem mit mehreren Threads. Wenn zwei Threads gleichzeitig versuchen, den Cache zu verlassen
Get
(oder zu überprüfenContains
), verfehlen beide den Cache und beide generieren am Ende Das Ergebnis und beide fügen das Ergebnis dann dem Cache hinzu.Oft ist dies unerwünscht - der zweite Thread sollte warten, bis der erste abgeschlossen ist, und sein Ergebnis verwenden, anstatt zweimal Ergebnisse zu generieren.
Dies war einer der Gründe, warum ich LazyCache geschrieben habe - einen benutzerfreundlichen Wrapper für MemoryCache, der diese Art von Problemen löst. Es ist auch auf Nuget erhältlich .
quelle
Wie bereits erwähnt, ist MemoryCache in der Tat threadsicher. Die Thread-Sicherheit der darin gespeicherten Daten hängt jedoch ganz von Ihrer Verwendung ab.
Um Reed Copsey aus seinem großartigen Beitrag über Parallelität und den
ConcurrentDictionary<TKey, TValue>
Typ zu zitieren . Welches ist natürlich hier anwendbar.Sie können sich vorstellen, dass dies besonders schlimm wäre, wenn
TValue
der Bau teuer wäre.Um dies zu umgehen, können Sie es
Lazy<T>
sehr einfach nutzen, was zufällig sehr billig zu konstruieren ist. Auf diese Weise stellen wir sicher, dass wir nur mehrere Instanzen vonLazy<T>
(was billig ist) erstellen, wenn wir in eine Multithread-Situation geraten .GetOrAdd()
(GetOrCreate()
im Fall vonMemoryCache
) gibt den gleichen SingularLazy<T>
für alle Threads zurück, die "zusätzlichen" Instanzen vonLazy<T>
werden einfach weggeworfen.Da das
Lazy<T>
nichts tut, bis.Value
es aufgerufen wird, wird immer nur eine Instanz des Objekts erstellt.Nun zu etwas Code! Unten finden Sie eine Erweiterungsmethode, für
IMemoryCache
die die oben genannten implementiert werden. Die EinstellungSlidingExpiration
basiert willkürlich auf einemint seconds
Methodenparameter. Dies ist jedoch vollständig an Ihre Bedürfnisse anpassbar.Anrufen:
Um dies alles asynchron durchzuführen, empfehle ich die Verwendung der hervorragenden
AsyncLazy<T>
Implementierung von Stephen Toub, die in seinem Artikel über MSDN zu finden ist. Was den eingebauten Lazy InitialisiererLazy<T>
mit dem Versprechen kombiniertTask<T>
:Jetzt die asynchrone Version von
GetOrAdd()
:Und zum Schluss:
quelle
MemoryCache.GetOrCreate
wird auf die gleiche Art und Weise nicht Thread sicherConcurrentDictionary
istLazy
? Wenn ja, wie haben Sie dies validiert?GetOrCreate
denselben Schlüssel und diese Factory zu verwenden. Das Ergebnis war, dass die Factory 10 Mal verwendet wurde, wenn sie mit dem Speichercache verwendet wurde (sah die Ausdrucke) + jedes Mal, wennGetOrCreate
ein anderer Wert zurückgegeben wurde! Ich habe den gleichen Test mit durchgeführtConcurrentDicionary
und gesehen, dass die Fabrik nur einmal benutzt wurde, und habe immer den gleichen Wert erhalten. Ich habe ein geschlossenes Problem in Github gefunden. Ich habe dort gerade einen Kommentar geschrieben, dass es wieder geöffnet werden sollÜberprüfen Sie diesen Link: http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx
Gehen Sie ganz nach unten auf der Seite (oder suchen Sie nach dem Text "Thread Safety").
Du wirst sehen:
quelle
MemoryCache.Default
sehr hohem Volumen (Millionen von Cache-Treffern pro Minute) ohne Threading-Probleme.Sie haben gerade eine Beispielbibliothek hochgeladen, um das Problem für .Net 2.0 zu beheben.
Schauen Sie sich dieses Repo an:
RedisLazyCache
Ich verwende den Redis-Cache, aber es ist auch ein Failover oder nur ein Memorycache, wenn der Verbindungsstring fehlt.
Es basiert auf der LazyCache-Bibliothek, die die einmalige Ausführung eines Rückrufs zum Schreiben im Falle eines Multithreading garantiert, bei dem versucht wird, Daten zu laden und zu speichern, insbesondere wenn die Ausführung des Rückrufs sehr teuer ist.
quelle
Wie von @AmitE bei der Antwort von @pimbrouwers erwähnt, funktioniert sein Beispiel nicht wie hier gezeigt:
Ausgabe:
Es scheint, als würde
GetOrCreate
immer der erstellte Eintrag zurückgegeben. Zum Glück ist das sehr einfach zu beheben:Das funktioniert wie erwartet:
quelle
Der Cache ist threadsicher, aber wie andere angegeben haben, ist es möglich, dass GetOrAdd die Funktion mehrere Typen aufruft, wenn mehrere Typen aufgerufen werden.
Hier ist meine minimale Lösung dafür
und
quelle