问题
While this question is about the MemoryCache
class, I can imagine the same need with a Dictionary
or ConcurrentDictionary.GetOrAdd
where the valueFactory
-lambda is also a lengthy operation.
In essence I want to synchronize/lock threads on a per-item base. I know MemoryCache
is thread safe, but still, checking if an item exists and add the item when it doesn't exist, still needs to be synchronized.
Consider this sample code:
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;
}
}
As you can see, we need to synchronize cache.GetCachedItem
and cache.Add
. But since CreateItem
is a lengthy operation (hence the MemoryCache
), I don't want to lock all threads as this code would do:
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)
});
}
}
Also, having no lock is not an options, as then we could have multiple threads calling CreateItem
for the same id
.
What I could do is create a unique named Semaphore
per id
, so locking happens on per-item basis. But this will be a system-resource killer, as we do not want to register +100K named semaphores on our system.
I'm sure I'm not the first that needs this kind of synchronization, but I didn't find any question/answer that fits this scenario.
My question is if someone can come up with a different, resource friendly approach for this problem?
Update
I've found this NamedReaderWriterLocker class that looks promising at first but is dangerous to use as two threads can potentially get a different ReaderWriterLockSlim
instance for the same name when both threads get into the ConcurrentDictionary
's valueFactory
at the same time. Maybe I can use this implementation with some additional lock inside the GetLock
method.
回答1:
Since your key is a string, you could lock on string.Intern(id)
.
MSDN documentation: System.String.Intern
i.e.
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. */;
}
来源:https://stackoverflow.com/questions/36839135/synchronize-threads-on-per-item-base