MemoryCache Thread Safety, Is Locking Necessary?

c#, memorycache, multithreading, wcf

Solution

The default MS-provided `MemoryCache` is entirely thread safe. Any custom implementation that derives from `MemoryCache` may not be thread safe. If you're using plain `MemoryCache` out of the box, it is thread safe. Browse the source code of my open source distributed caching solution to see how I use it (MemCache.cs):

https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs

Problem

For starters let me just throw it out there that I know the code below is not thread safe (correction: might be). What I am struggling with is finding an implementation that is and one that I can actually get to fail under test. I am refactoring a large WCF project right now that needs some (mostly) static data cached and its populated from a SQL database. It needs to expire and "refresh" at least once a day which is why I am using MemoryCache. I know that the code below should not be thread safe but I cannot get it to fail under heavy load and to complicate matters a google search shows implementations both ways (with and without locks combined with debates whether or not they are necessary. Could someone with knowledge of MemoryCache in a multi threaded environment let me definitively know whether or not I need to lock where appropriate so that a call to remove (which will seldom be called but its a requirement) will not throw during retrieval/repopulation. ``` 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; } } ```

Original source

Related problems