C# MemoryCache线程安全,是否需要锁定?

C# MemoryCache线程安全,是否需要锁定?,c#,multithreading,wcf,memorycache,C#,Multithreading,Wcf,Memorycache,首先,让我把它扔出去,我知道下面的代码不是线程安全的(更正:可能是)。我正在努力寻找一个在测试中可能失败的实现。我现在正在重构一个大型WCF项目,该项目需要缓存一些(大部分)静态数据,并从SQL数据库填充这些数据。它需要过期并每天至少“刷新”一次,这就是我使用MemoryCache的原因 我知道下面的代码不应该是线程安全的,但我不能让它在重载情况下失败,也不能让事情变得复杂。谷歌搜索显示了两种实现方式(带锁和不带锁,并讨论它们是否必要) 如果有人了解多线程环境中的MemoryCache,请告诉我

首先,让我把它扔出去,我知道下面的代码不是线程安全的(更正:可能是)。我正在努力寻找一个在测试中可能失败的实现。我现在正在重构一个大型WCF项目,该项目需要缓存一些(大部分)静态数据,并从SQL数据库填充这些数据。它需要过期并每天至少“刷新”一次,这就是我使用MemoryCache的原因

我知道下面的代码不应该是线程安全的,但我不能让它在重载情况下失败,也不能让事情变得复杂。谷歌搜索显示了两种实现方式(带锁和不带锁,并讨论它们是否必要)

如果有人了解多线程环境中的MemoryCache,请告诉我是否需要在适当的情况下锁定,以便在检索/重新填充期间不会抛出删除调用(虽然很少调用,但这是一项要求)

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;
    }
}
公共类MemoryCacheService:IMemoryCacheService
{
私有常量字符串标点符号MapCacheKey=“标点符号映射”;
私有静态只读对象缓存;
私有只读IAdoNet;
静态内存缓存服务()
{
Cache=MemoryCache.Default;
}
公共内存缓存服务(IAdoNet adoNet)
{
_adoNet=adoNet;
}
public void clearportionmaps()
{
Cache.Remove(标点映射cachekey);
}
public IEnumerable GetPercentrationMaps()
{
if(Cache.Contains(标点MapCacheKey))
{
return(IEnumerable)Cache.Get(标点MapCacheKey);
}
var percentrationmaps=getpercentrationmappings();
if(标点映射==null)
{
抛出新的ApplicationException(“无法从数据库检索标点符号映射”);
}
if(标点映射.Cast().Any(p=>p.UntagedValue==null | | p.TaggedValue==null))
{
抛出新的ApplicationException(“在未标记或标记的标点符号映射中检测到空值”);
}
//将数据存储在缓存中
var cacheItemPolicy=新的cacheItemPolicy
{
AbsoluteExpiration=DateTime.Now.AddDays(1.0)
};
AddOrGetExisting(标点符号MapCacheKey、标点符号Maps、cacheItemPolicy);
返回标点图;
}
//转到oldschool ADO.NET以打破对实体框架的依赖,并需要注入数据库处理程序以填充缓存
private IEnumerable Get标点映射()
{
var table=_adoNet.ExecuteSelectCommand(“从[dbo].[标点映射器]]中选择[id]、[TaggedValue]、[UntagedValue]”,CommandType.Text);
if(table!=null&&table.Rows.Count!=0)
{
返回AutoMapper.Mapper.DynamicMap(table.CreateDataReader());
}
返回null;
}
}

提供的默认MS
MemoryCache
完全是线程安全的。从
MemoryCache
派生的任何自定义实现可能都不是线程安全的。如果您使用的是现成的普通
MemoryCache
,它是线程安全的。请浏览我的开源分布式缓存解决方案的源代码,了解我是如何使用它的(MemCache.cs):

查看此链接:

转到页面的最底部(或搜索文本“线程安全”)

你会看到:

^线程安全性

这种类型是线程安全的


虽然MemoryCache确实是线程安全的,正如其他答案所指定的那样,但它确实存在一个常见的多线程问题—如果两个线程尝试从中获取(或检查
包含
)同时删除缓存,则两者都将丢失缓存,并最终生成结果,然后将结果添加到缓存中

通常这是不可取的-第二个线程应该等待第一个线程完成并使用其结果,而不是生成两次结果


这就是我写这篇文章的原因之一——MemoryCache上的友好包装解决了这类问题。正如其他人所说,MemoryCache确实是线程安全的。然而,存储在其中的数据的线程安全性完全取决于您对它的使用

引用他关于并发性和
ConcurrentDictionary
类型的绝妙见解。这当然适用于这里

如果两个线程同时调用这个[GetOrAdd],那么TValue的两个实例就可以很容易地构造出来

您可以想象,如果
TValue
的构造成本很高,那么这将特别糟糕

为了解决这个问题,您可以非常轻松地利用
Lazy
,这恰好是非常便宜的构造方法。这样做可以确保在进入多线程环境时,我们只构建了
Lazy
的多个实例(这很便宜)

GetOrAdd()
GetOrCreate()
MemoryCache
的情况下)将向所有线程返回相同的、单数的
Lazy
,而
Lazy
的“额外”实例将被简单地丢弃

由于在调用
.Value
之前,Lazy
不会执行任何操作,因此只会构造对象的一个实例

下面是实现上述功能的
IMemoryCache
的扩展方法。它根据
int seconds
方法参数任意设置
SlidingExpiration
。但这完全可以根据您的需要定制

注意:这是特定于.netcore2.0应用程序的

为了异步执行这一切,我建议使用在MSDN上的his中找到的优秀的
AsyncLazy
实现。它将内置的惰性初始值设定项
lazy
与promise
任务相结合:

最后,打电话:

IMemoryCache cache;
var result = await cache.GetOrAddAsync("someKey", 60, async () => new object());

刚刚上载了示例库以解决.Ne的问题
IMemoryCache cache;
var result = cache.GetOrAdd("someKey", 60, () => new object());
public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<T> valueFactory) :
        base(() => Task.Factory.StartNew(valueFactory))
    { }
    public AsyncLazy(Func<Task<T>> taskFactory) :
        base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
    { }
}   
public static Task<T> GetOrAddAsync<T>(this IMemoryCache cache, string key, int seconds, Func<Task<T>> taskFactory)
{
    return cache.GetOrCreateAsync<T>(key, async entry => await new AsyncLazy<T>(async () =>
    { 
        entry.SlidingExpiration = TimeSpan.FromSeconds(seconds);

        return await taskFactory.Invoke();
    }).Value);
}
IMemoryCache cache;
var result = await cache.GetOrAddAsync("someKey", 60, async () => new object());
class Program
{
    static async Task Main(string[] args)
    {
        var cache = new MemoryCache(new MemoryCacheOptions());

        var tasks = new List<Task>();
        var counter = 0;

        for (int i = 0; i < 10; i++)
        {
            var loc = i;
            tasks.Add(Task.Run(() =>
            {
                var x = GetOrAdd(cache, "test", TimeSpan.FromMinutes(1), () => Interlocked.Increment(ref counter));
                Console.WriteLine($"Interation {loc} got {x}");
            }));
        }

        await Task.WhenAll(tasks);
        Console.WriteLine("Total value creations: " + counter);
        Console.ReadKey();
    }

    public static T GetOrAdd<T>(IMemoryCache cache, string key, TimeSpan expiration, Func<T> valueFactory)
    {
        return cache.GetOrCreate(key, entry =>
        {
            entry.SetSlidingExpiration(expiration);
            return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
        }).Value;
    }
}
Interation 6 got 8
Interation 7 got 6
Interation 2 got 3
Interation 3 got 2
Interation 4 got 10
Interation 8 got 9
Interation 5 got 4
Interation 9 got 1
Interation 1 got 5
Interation 0 got 7
Total value creations: 10
public static T GetOrSetValueSafe<T>(IMemoryCache cache, string key, TimeSpan expiration,
    Func<T> valueFactory)
{
    if (cache.TryGetValue(key, out Lazy<T> cachedValue))
        return cachedValue.Value;

    cache.GetOrCreate(key, entry =>
    {
        entry.SetSlidingExpiration(expiration);
        return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
    });

    return cache.Get<Lazy<T>>(key).Value;
}
Interation 4 got 1
Interation 9 got 1
Interation 1 got 1
Interation 8 got 1
Interation 0 got 1
Interation 6 got 1
Interation 7 got 1
Interation 2 got 1
Interation 5 got 1
Interation 3 got 1
Total value creations: 1
private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1);
await _cacheLock.WaitAsync();
var data = await _cache.GetOrCreateAsync(key, entry => ...);
_cacheLock.Release();