38 Stimmen

Caching in C#/.Net

Ich wollte Sie fragen, was ist der beste Ansatz, um einen Cache in C# zu implementieren? Gibt es eine Möglichkeit, indem man bestimmte .NET-Klassen oder etwas Ähnliches verwendet? Vielleicht so etwas wie ein Wörterbuch, das einige Einträge entfernt, wenn es zu groß wird, aber wo deren Einträge nicht durch den Garbage Collector entfernt werden?

2 Stimmen

Es kommt darauf an schwer in der Anwendung. Wofür verwenden Sie es?

0 Stimmen

Nicht in einer asp.net Art und Weise, aber ich weiß noch nicht genau, ich werde die Anforderungen posten, wenn ich sie habe, aber danke für Ihre erste Antwort :)

0 Stimmen

Robustes .NET-Caching behandelt häufige Fallstricke des Caching und stellt eine Bibliothek zur Verfügung, die Entwicklern hilft, einige der üblichen Fallstricke zu vermeiden. Der Beitrag erklärt insbesondere, wie Sie SpeicherCache sicher.

29voto

Muaddib Punkte 475

Wenn Sie .NET 4 oder höher verwenden, können Sie SpeicherCache classe.

20voto

Bravax Punkte 10341

Wenn Sie ASP.NET verwenden, können Sie die Cache Klasse ( System.Web.Caching ).

Hier ist eine gute Hilfsklasse: c-cache-helper-class

Wenn Sie das Zwischenspeichern in einer Windows-Formularanwendung meinen, hängt es davon ab, was Sie tun möchten und wo Sie versuchen, die Daten zwischenzuspeichern.

Wir haben einen Cache hinter einem Webservice für bestimmte Methoden implementiert
(unter Verwendung der System.Web.Caching Objekt.).

Sie sollten sich jedoch auch den Caching Application Block ansehen. ( Siehe hier) die Teil der Enterprise Library für .NET Framework 2.0 ist.

2 Stimmen

Es wird nicht gesagt, dass er asp.net verwendet

2 Stimmen

Ich habe sowohl eine Option für asp.net als auch einen Ansatz für den Fall vorgeschlagen, dass Sie es nicht sind. (Caching Application Block).

0 Stimmen

Es spielt keine Rolle, ob es asp.net ist oder nicht. Sie können System.Web in einer Desktop-Anwendung referenzieren und System.Web.Cache über die Eigenschaft HttpRuntime.Cache verwenden.

5voto

alastairtree Punkte 3535

SpeicherCache im Framework ist ein guter Startpunkt, aber Sie könnten auch die Open-Source-Bibliothek LazyCache weil er eine einfachere API als der Memory Cache hat und über ein eingebautes Locking sowie einige andere entwicklerfreundliche Funktionen verfügt. Er ist auch in Nuget verfügbar.

Um Ihnen ein Beispiel zu geben:

// Create our cache service using the defaults (Dependency injection ready).
// Uses MemoryCache.Default as default so cache is shared between instances
IAppCache cache = new CachingService();

// Declare (but don't execute) a func/delegate whose result we want to cache
Func<ComplexObjects> complexObjectFactory = () => methodThatTakesTimeOrResources();

// Get our ComplexObjects from the cache, or build them in the factory func 
// and cache the results for next time under the given key
ComplexObject cachedResults = cache.GetOrAdd("uniqueKey", complexObjectFactory);

Vor kurzem habe ich diesen Artikel über erste Schritte mit Caching in Dot Net die Sie vielleicht nützlich finden.

(Haftungsausschluss: Ich bin der Autor von LazyCache)

0 Stimmen

Eine einfachere API als MemoryCache? Das kann ich kaum glauben, denn es ist wohl die einfachste API, die Microsoft zu bieten hat. Ich versuche auch herauszufinden, warum Sie eine völlig neue Caching-API entwickelt haben? Es sieht gut aus und so, aber warum haben Sie den ganzen Aufwand für etwas betrieben, das es schon seit Ewigkeiten gibt und stabil ist.

2 Stimmen

Es handelt sich dabei nicht um eine massive Änderung des Speicher-Cache, sondern um die Lösung des Gleichzeitigkeitsproblems und die Verringerung der Duplizierung durch einzeilige, kassierbare Delegierte. Nach dem Schreiben der gleichen groben Code mehrmals spart es Zeit für mich mit einer Bibliothek, und das ist gut genug für mich.

3voto

itadapter DKh Punkte 596

Die Cache-Klassen, die mit .NET geliefert werden, sind praktisch, haben aber ein großes Problem - sie können nicht viele Daten (zehn Millionen+) von Objekten für eine lange Zeit speichern, ohne Ihre GC zu töten. Sie funktionieren großartig, wenn Sie ein paar tausend Objekte zwischenspeichern, aber in dem Moment, in dem Sie in Millionen bewegen und halten Sie sie um, bis sie in GEN2 propagieren - die GC-Pausen würde schließlich beginnen, spürbar zu sein, wenn Sie System kommt zu niedrigen Speicherschwelle und GC muss alle gens zu fegen.

Wenn Sie ein paar hunderttausend Instanzen speichern müssen, sollten Sie den MS-Cache verwenden. Es spielt keine Rolle, ob Ihre Objekte 2 Felder oder 25 Felder umfassen - es geht um die Anzahl der Verweise.

Andererseits gibt es Fälle, in denen große Arbeitsspeicher, wie sie heutzutage üblich sind, genutzt werden müssen, z. B. 64 GB. Hierfür haben wir einen zu 100 % verwalteten Speichermanager und einen darauf aufbauenden Cache geschaffen.

Unsere Lösung kann problemlos 300.000.000 Objekte prozessbegleitend im Speicher speichern, ohne GC zu belasten, da wir die Daten in großen (250 MB) Byte[]-Segmenten speichern.

Hier ist der Code: NFX-Stapel (Apache 2.0)

Und Video: NFX Stapel-Cache - Youtube

2voto

Alex Peck Punkte 4505

Wie bereits in anderen Antworten erwähnt, ist die Standardauswahl bei Verwendung des .NET Frameworks SpeicherCache und die verschiedenen zugehörigen Implementierungen in Microsoft NuGet-Paketen (z. B. Microsoft.Extensions.Caching.MemoryCache ). Alle diese Caches haben eine Größenbeschränkung in Bezug auf den verbrauchten Speicher und versuchen, den verbrauchten Speicher abzuschätzen, indem sie verfolgen, wie der gesamte physische Speicher im Verhältnis zur Anzahl der zwischengespeicherten Objekte wächst. Ein Hintergrund-Thread "trimmt" dann regelmäßig Einträge.

MemoryCache usw. haben einige Einschränkungen:

  1. Schlüssel sind Zeichenketten. Wenn der Schlüsseltyp also nicht von Haus aus eine Zeichenkette ist, sind Sie gezwungen, ständig Zeichenketten auf dem Heap zuzuweisen. Dies kann sich in einer Serveranwendung wirklich summieren, wenn Elemente "heiß" sind.
  2. Hat eine geringe "Scan-Resistenz" - z.B. wenn ein automatisierter Prozess schnell durch alle vorhandenen Elemente läuft, kann die Cache-Größe zu schnell wachsen, so dass der Hintergrund-Thread nicht mithalten kann. Dies kann zu Speicherdruck, Seitenfehlern, induziertem GC oder, wenn der Prozess unter IIS läuft, zum Recyceln des Prozesses aufgrund des Überschreitens des Limits für private Bytes führen.
  3. Skaliert nicht gut mit gleichzeitigen Schreibvorgängen.
  4. Enthält Perf-Counter, die nicht deaktiviert werden können (und somit Overhead verursachen).

Inwieweit diese Dinge problematisch sind, hängt von der Arbeitsbelastung ab. Ein alternativer Ansatz für die Zwischenspeicherung besteht darin, die Anzahl der Objekte im Cache zu begrenzen (anstatt den verwendeten Speicher zu schätzen). A Cache-Ersatzpolitik bestimmt dann, welches Objekt verworfen werden soll, wenn der Cache voll ist.

Nachfolgend finden Sie den Quellcode für einen einfachen Cache mit Least Recently Used Eviction Policy:

public sealed class ClassicLru<K, V>
{
    private readonly int capacity;
    private readonly ConcurrentDictionary<K, LinkedListNode<LruItem>> dictionary;
    private readonly LinkedList<LruItem> linkedList = new LinkedList<LruItem>();

    private long requestHitCount;
    private long requestTotalCount;

    public ClassicLru(int capacity)
        : this(Defaults.ConcurrencyLevel, capacity, EqualityComparer<K>.Default)
    { 
    }

    public ClassicLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer)
    {
        if (capacity < 3)
        {
            throw new ArgumentOutOfRangeException("Capacity must be greater than or equal to 3.");
        }

        if (comparer == null)
        {
            throw new ArgumentNullException(nameof(comparer));
        }

        this.capacity = capacity;
        this.dictionary = new ConcurrentDictionary<K, LinkedListNode<LruItem>>(concurrencyLevel, this.capacity + 1, comparer);
    }

    public int Count => this.linkedList.Count;

    public double HitRatio => (double)requestHitCount / (double)requestTotalCount;

    ///<inheritdoc/>
    public bool TryGet(K key, out V value)
    {
        Interlocked.Increment(ref requestTotalCount);

        LinkedListNode<LruItem> node;
        if (dictionary.TryGetValue(key, out node))
        {
            LockAndMoveToEnd(node);
            Interlocked.Increment(ref requestHitCount);
            value = node.Value.Value;
            return true;
        }

        value = default(V);
        return false;
    }

    public V GetOrAdd(K key, Func<K, V> valueFactory)
    {
        if (this.TryGet(key, out var value))
        {
            return value;
        }

        var node = new LinkedListNode<LruItem>(new LruItem(key, valueFactory(key)));

        if (this.dictionary.TryAdd(key, node))
        {
            LinkedListNode<LruItem> first = null;

            lock (this.linkedList)
            {
                if (linkedList.Count >= capacity)
                {
                    first = linkedList.First;
                    linkedList.RemoveFirst();
                }

                linkedList.AddLast(node);
            }

            // Remove from the dictionary outside the lock. This means that the dictionary at this moment
            // contains an item that is not in the linked list. If another thread fetches this item, 
            // LockAndMoveToEnd will ignore it, since it is detached. This means we potentially 'lose' an 
            // item just as it was about to move to the back of the LRU list and be preserved. The next request
            // for the same key will be a miss. Dictionary and list are eventually consistent.
            // However, all operations inside the lock are extremely fast, so contention is minimized.
            if (first != null)
            {
                dictionary.TryRemove(first.Value.Key, out var removed);

                if (removed.Value.Value is IDisposable d)
                {
                    d.Dispose();
                }
            }

            return node.Value.Value;
        }

        return this.GetOrAdd(key, valueFactory);
    }

    public bool TryRemove(K key)
    {
        if (dictionary.TryRemove(key, out var node))
        {
            // If the node has already been removed from the list, ignore.
            // E.g. thread A reads x from the dictionary. Thread B adds a new item, removes x from 
            // the List & Dictionary. Now thread A will try to move x to the end of the list.
            if (node.List != null)
            {
                lock (this.linkedList)
                {
                    if (node.List != null)
                    {
                        linkedList.Remove(node);
                    }
                }
            }

            if (node.Value.Value is IDisposable d)
            {
                d.Dispose();
            }

            return true;
        }

        return false;
    }

    // Thead A reads x from the dictionary. Thread B adds a new item. Thread A moves x to the end. Thread B now removes the new first Node (removal is atomic on both data structures).
    private void LockAndMoveToEnd(LinkedListNode<LruItem> node)
    {
        // If the node has already been removed from the list, ignore.
        // E.g. thread A reads x from the dictionary. Thread B adds a new item, removes x from 
        // the List & Dictionary. Now thread A will try to move x to the end of the list.
        if (node.List == null)
        {
            return;
        }

        lock (this.linkedList)
        {
            if (node.List == null)
            {
                return;
            }

            linkedList.Remove(node);
            linkedList.AddLast(node);
        }
    }

    private class LruItem
    {
        public LruItem(K k, V v)
        {
            Key = k;
            Value = v;
        }

        public K Key { get; }

        public V Value { get; }
    }
}

Dies dient nur zur Veranschaulichung eines thread-sicheren Caches - er ist wahrscheinlich fehlerhaft und kann bei hoher gleichzeitiger Arbeitslast (z.B. in einem Webserver) ein Engpass sein.

Eine gründlich getestete, produktionsreife, skalierbare, gleichzeitige Implementierung geht ein wenig über einen Stack Overflow-Beitrag hinaus. Um dieses Problem in meinen Projekten zu lösen, implementierte ich eine Thread-sichere Pseudo-LRU (denken Sie nebenläufige Wörterbuch, aber mit eingeschränkter Größe). Die Leistung ist sehr nah an einem rohen ConcurrentDictionary, ~10x schneller als MemoryCache, ~10x besserer gleichzeitiger Durchsatz als ClassicLru oben, und bessere Trefferquote. Eine detaillierte Leistungsanalyse in der Github-Link unten zur Verfügung gestellt.

Die Verwendung sieht folgendermaßen aus:

int capacity = 666;
var lru = new ConcurrentLru<int, SomeItem>(capacity);

var value = lru.GetOrAdd(1, (k) => new SomeItem(k));

GitHub: https://github.com/bitfaster/BitFaster.Caching

Install-Package BitFaster.Caching

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X