2016-04-25 7 views
1

Während diese Frage ist über die Klasse, kann ich mir vorstellen, die gleiche Notwendigkeit mit einer Dictionary oder ConcurrentDictionary.GetOrAdd, wo die valueFactory -Lambda ist auch eine langwierige Operation.Synchronisieren Sie Threads auf Artikel Basis

Im Wesentlichen möchte ich Threads auf einer Artikelbasis synchronisieren/sperren. Ich weiß, MemoryCache ist Thread sicher, aber immer noch, prüfen, ob ein Element vorhanden ist und das Element hinzufügen, wenn es nicht vorhanden ist, muss noch synchronisiert werden.

Betrachten Sie diesen Beispielcode:

public class MyCache 
{ 
    private static readonly MemoryCache cache = new MemoryCache(Guid.NewGuid().ToString()); 

    public object Get(string id) 
    { 
     var cacheItem = cache.GetCachedItem(id); 
     if (cacheItem != null) return cacheItem.Value; 
     var item = this.CreateItem(id); 
     cache.Add(id, item, new CacheItemPolicy 
     { 
      SlidingExpiration = TimeSpan.FromMinutes(20) 
     }); 
     return item; 
    } 

    private object CreateItem(string id) 
    { 
     // Lengthy operation, f.e. querying database or even external API 
     return whateverCreatedObject; 
    } 
} 

Wie Sie sehen können, brauchen wir cache.GetCachedItem und cache.Add zu synchronisieren. Aber da CreateItem ist ein langwieriger Vorgang (daher die MemoryCache), will ich nicht alle Themen sperren, wie dieser Code tun würde:

public object Get(string id) 
{ 
    lock (cache) 
    { 
     var item = cache.GetCachedItem(id); 
     if (item != null) return item.Value; 
     cache.Add(id, this.CreateItem(id), new CacheItemPolicy 
     { 
      SlidingExpiration = TimeSpan.FromMinutes(20) 
     }); 
    } 
} 

Auch kein Schloss ist keine glücklichen Optionen, wie könnten wir haben mehrere Threads aufrufen CreateItem für die gleiche id.

Was ich tun könnte, ist ein eindeutiger Name Semaphore pro id erstellen, so dass die Sperrung auf Artikelbasis erfolgt. Aber das wird ein System-Ressourcen-Killer sein, da wir nicht + 100K benannte Semaphore auf unserem System registrieren wollen.

Ich bin mir sicher, dass ich nicht der Erste bin, der diese Art der Synchronisation benötigt, aber ich habe keine Frage/Antwort gefunden, die zu diesem Szenario passt.

Meine Frage ist, ob jemand mit einem anderen, ressourcenfreundlichen Ansatz für dieses Problem kommen kann?

aktualisieren

I this NamedReaderWriterLocker Klasse gefunden haben, die auf den ersten vielversprechend aussieht, ist aber gefährlich, da zwei Threads zu verwenden, kann möglicherweise eine andere ReaderWriterLockSlim Instanz für den gleichen Namen erhalten, wenn beide Threads in die ConcurrentDictionary ‚s bekommen valueFactory gleichzeitig. Vielleicht kann ich diese Implementierung mit einem zusätzlichen Lock innerhalb der GetLock Methode verwenden.

+0

'ConcurrentDictionary' verwendet' Versuche * '/' * oder * 'Muster in seiner API: https://msdn.microsoft.com/en- usa/library/dd287191% 28v = vs.110% 29.aspx – Dennis

+0

Ja, 'GetOrAdd' ist irgendwie das selbe, aber ich wette'ConcurrentDictionary' sperrt auch das komplette Wörterbuch, während das add lambda' valueFactory' ausgeführt wird, und tut es nicht Verwenden Sie eine Tastensperre. –

+0

AFAIK, 'ConcurrentDictionary' ist sperrfrei. – Dennis

Antwort

2

Da Ihr Schlüssel eine Zeichenfolge ist, können Sie auf string.Intern(id) sperren.

MSDN-Dokumentation: System.String.Intern

dh

lock (string.Intern(id)) 
{ 
    var item = cache.GetCachedItem(id); 
    if (item != null) 
    { 
     return item.Value; 
    } 

    cache.Add(id, this.CreateItem(id), new CacheItemPolicy 
    { 
     SlidingExpiration = TimeSpan.FromMinutes(20) 
    }); 

    return /* some value, this line was absent in the original code. */; 
} 
+0

Hatte keine Ahnung von der Existenz von 'string.Intern', sehr interessant. Auf der anderen Seite halte ich das für einen sehr kniffligen Ansatz. –

+0

Es ist, und es wird Sie mit einem Speicheraufwand verlassen, der nicht verschwindet, bis der Prozess beendet wird. – yaakov

+0

Es ist ein ziemlich solider Ansatz, wenn Sie sicher sein können, dass diese Strings nicht blockiert sind. Der Overhead sollte normalerweise akzeptabel sein. –