2015-12-13 7 views
12

Ich bin neu in C# und versuche zu verstehen, wie man mit Lazy arbeitet.Wie Lazy verwenden, um gleichzeitige Anfrage zu behandeln?

Ich muss gleichzeitige Anfrage behandeln, indem ich auf das Ergebnis einer bereits laufenden Operation warte. Datenanforderungen können gleichzeitig mit denselben/unterschiedlichen Anmeldeinformationen eingehen.

Für jeden eindeutigen Satz von Anmeldeinformationen gibt es höchstens einen GetDataInternal Anruf im Gang, mit dem Ergebnis sein kann, von dem ein Aufruf an alle Warteschlangen Kellner zurück, wenn es fertig ist

private readonly ConcurrentDictionary<Credential, Lazy<Data>> Cache 
= new ConcurrentDictionary<Credential, Lazy<Data>>(); 

public Data GetData(Credential credential) 
{ 
    // This instance will be thrown away if a cached 
    // value with our "credential" key already exists. 
    Lazy<Data> newLazy = new Lazy<Data>(
     () => GetDataInternal(credential), 
     LazyThreadSafetyMode.ExecutionAndPublication 
    ); 

    Lazy<Data> lazy = Cache.GetOrAdd(credential, newLazy); 
    bool added = ReferenceEquals(newLazy, lazy); // If true, we won the race. 
    Data data; 

    try 
    { 
     // Wait for the GetDataInternal call to complete. 
     data = lazy.Value; 
    } 
    finally 
    { 
     // Only the thread which created the cache value 
     // is allowed to remove it, to prevent races. 
     if (added) { 
      Cache.TryRemove(credential, out lazy); 
     } 
    } 

    return data; 
} 

Ist das richtig zu nutzen Lazy oder mein Code ist nicht sicher?


Update:

Ist es gute Idee, mit MemoryCache statt ConcurrentDictionary zu beginnen? Wenn ja, wie erstellt man einen Schlüsselwert, weil es ein string innerhalb MemoryCache.Default.AddOrGetExisting()

Antwort

5

ist Dies ist richtig. Dies ist ein Standardmuster (mit Ausnahme des Entfernens) und es ist ein wirklich guter Cache, weil es Cache-Ansturm verhindert.

Ich bin nicht sicher, dass Sie aus dem Cache entfernen möchten, wenn die Berechnung durchgeführt wird, da die Berechnung immer wieder neu erstellt wird. Wenn Sie das Entfernen nicht benötigen, können Sie den Code vereinfachen, indem Sie die zweite Hälfte löschen.

Beachten Sie, dass Lazy ein Problem im Falle einer Ausnahme hat: Die Ausnahme wird gespeichert und die Fabrik wird nie erneut ausgeführt werden. Das Problem bleibt für immer bestehen (bis ein Mensch die App neu startet). Meiner Meinung nach ist dies Lazy in den meisten Fällen völlig ungeeignet für den Produktionseinsatz.

Dies bedeutet, dass ein vorübergehender Fehler wie ein Netzwerkproblem die App dauerhaft nicht verfügbar machen kann.

+0

Also, wenn ich 'faul' habe, kann ich nur' faul.Value' zurückgeben? Und nicht aus dem Cache entfernen –

+0

Ja .___________ – usr

+0

Wenn Sie 'PublicationOnly' verwenden, erhalten Sie möglicherweise keine Thread-Sicherheit für 'valueFactory', aber Ausnahmen werden nicht zwischengespeichert. –

1

Diese Antwort bezieht sich auf den aktualisierten Teil der ursprünglichen Frage. Siehe @usr answer in Bezug auf Thread-Sicherheit mit Lazy<T> und die möglichen Fallstricke.


Ich mag würde wissen, wie ConcurrentDictionary<TKey, TValue> Verwendung zu vermeiden und starten Memory mit? Wie zu implementieren MemoryCache.Default.AddOrGetExisting()?

Wenn Sie einen Cache freuen, die einen Mechanismus für die automatische Ablauf hat, dann ist MemoryCache eine gute Wahl, wenn Sie nicht wollen, die Mechanik selbst implementieren.

Um MemoryCache zu verwenden, das eine Zeichenfolgendarstellung für einen Schlüssel erzwingt, müssen Sie eine eindeutige Zeichenfolgendarstellung eines Berechtigungsnachweises erstellen, möglicherweise eine bestimmte Benutzer-ID oder einen eindeutigen Benutzernamen?

Wenn Sie können, können Sie eine Überschreibung von ToString schaffen, oder einfach Ihre eindeutige Kennung repräsentiert die genannte Eigenschaft verwenden, und MemoryCache wie folgt nutzen:

public class Credential 
{ 
    public Credential(int userId) 
    { 
     UserId = userId; 
    } 

    public int UserId { get; private set; } 
} 

Und jetzt Ihre Methode wird wie folgt aussehen:

private const EvictionIntervalMinutes = 10; 
public Data GetData(Credential credential) 
{ 
    Lazy<Data> newLazy = new Lazy<Data>(
     () => GetDataInternal(credential), LazyThreadSafetyMode.ExecutionAndPublication); 

    CacheItemPolicy evictionPolicy = new CacheItemPolicy 
    { 
     AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(EvictionIntervalMinutes) 
    }; 

    var result = MemoryCache.Default.AddOrGetExisting(
     new CacheItem(credential.UserId.ToString(), newLazy), evictionPolicy); 

    return result != null ? ((Lazy<Data>)result.Value).Value : newLazy.Value; 
} 

MemoryCache liefern Sie eine Thread-sichere Implementierung, bedeutet dies, dass zwei Threads AddOrGetExisting Zugriff auf nur ein einziges Cache-Element verursachen hinzugefügt oder abgerufen werden. Ferner garantiert Lazy<T> mit ExecutionAndPublication nur einen einzigen eindeutigen Aufruf der Factory-Methode.

+0

Mit 'ConccurentDictionary' sieht es einfacher aus. Für mich ist es ein bisschen schwierig zu verstehen, warum wir zum Beispiel ".Value" zweimal machen müssen. –

+0

Vielleicht können wir einen anderen Konstruktor verwenden? 'private static string GetKey (Berechtigungsnachweise) { Rückgabezeichenfolge.Format (CultureInfo.InvariantCulture," {0} _ {1} ", typeof (Daten) .Name, Anmeldeinformationen); } ' –

+0

so etwas wie dieses –

Verwandte Themen