简体   繁体   English

这是清除C#MemoryCache的好方法吗?

[英]Is this a good solution to clear a C# MemoryCache?

I have read the questions and answers I could find about clearing the MemoryCache in C#. 我已经阅读了有关在C#中清除MemoryCache的问题和答案。 There were many recommendations, like: 1. Enumerate the cache, and remove all items - according to others, this is not good, getting the enumerator locks up the entire thing, and all kinds of apocalypse happens, quoting from a part of the documentation, I have not found, and showing a warning, I have failed to reproduce. 有许多建议,例如:1。枚举缓存,并删除所有项目 - 根据其他人,这不好,让枚举器锁定整个事情,并发生各种启示录,引用文档的一部分,我没有找到,并显示警告,我没有重现。 Anyways, I don't think this is a very efficient solution. 无论如何,我认为这不是一个非常有效的解决方案。

  1. Store the keys in a separate collection, and iterate that one, to remove the items from the cache. 将密钥存储在单独的集合中,并迭代该密钥,以从缓存中删除项目。 - Apart from this doesn't sound very thread safe, it doesn't sound very efficient also. - 除了这听起来不是很安全,它听起来也不是很有效。

  2. Dispose the old cache, and create a new one - This sounds good, the obvious problem that comes to mind, and is pointed out in several comments, that existing references to the old cache can bring up problems. 处理旧缓存,并创建一个新缓存 - 听起来不错,这是一个明显的问题,并且在几条评论中指出,现有的旧缓存引用可能会带来问题。 Of course the order of actions matters, you have to save a reference for the old one, create a new one in place of the old one, and dispose the old one - not everyone seemed to notice this little nuance. 当然,行动的顺序很重要,你必须为旧的参考保存一个参考,创建一个旧的参考代替旧的参考,并处理旧的 - 不是每个人似乎都注意到这个小细微差别。

So what now? 所以现在怎么办? One of my colleagues suggested using the MemoryCache for a problem of mine, and he wrapped the cache object in another class, wich has the option to get a key (loading it up from db if needed), remove a key, or clear the cache. 我的一位同事建议使用MemoryCache来解决我的问题,他将缓存对象包装在另一个类中,可以选择获取密钥(如果需要可以从db加载),删除密钥或清除缓存。 The first two are not important right now, the third one is interesting. 前两个现在不重要,第三个很有趣。 I have used the third solution for this, along the lines of "it is guaranteed, to not have any additional references to the MemoryCache object apart from my own implementation's". 我已经使用了第三个解决方案,除了“我保证,除了我自己的实现之外,没有任何对MemoryCache对象的额外引用”。 So relevant code is: 所以相关的代码是:

Constructor: 构造函数:

public MyCache()
{
    _cache = new MemoryCache("MyCache");
}

ClearCache: 清除缓存:

public void ClearCacheForAllUsers()
{
    var oldCache = _cache;
    _cache = new MemoryCache("MyCache");
    oldCache.Dispose();
}

_cache is the private MemoryCache object. _cache是​​私有的MemoryCache对象。

Can this cause problems in a multi-threaded environment? 这会在多线程环境中引起问题吗? I have some concerns of calling read and dispose in parallel. 我有一些关于并行调用read和dispose的问题。 Should I implement some kind of locking mechanism, wich allows concurrent reads, but causes the clear cache function to wait for current reads finishing on the cache? 我应该实现某种锁定机制,它允许并发读取,但是导致清除缓存功能等待当前读取在缓存上完成?

My guess is yes, it is required to implement this, but I'd like to get some insight before getting to it. 我的猜测是肯定的,它需要实现这一点,但我想在获得它之前获得一些见解。

Robert 罗伯特

EDIT 1 on Voo's answer: It is guaranteed, that noone outside of the "wrapper" (MyCache) will get a reference to the _cache object. 在Voo的回答中编辑1:保证,“包装器”(MyCache)之外的任何人都不会获得对_cache对象的引用。 My worry is something like the following: 我的担心如下:

T1:
MyCache.ClearAll()
    var oldCache = _cache
T2:
MyCache.GetOrReadFromDb("stuff")
T1:
    _cache=new MemoryCache("MyCache")
    oldCache.Dispose()
T2:
    *something bad*

Apart from the T2 thread still using the old cache, wich is not preferred, but something I can live with, can there be a situation where the Get method somehow accesses the old cache in a disposed state, or read the new one without the data? 除了仍然使用旧缓存的T2线程之外,这不是首选,但我可以使用的东西,可能存在这样的情况:Get方法以某种方式访问​​处于已处置状态的旧缓存,或者读取没有数据的新缓存?

Imagine the GetOrReadFromDb function something like this: 想象一下GetOrReadFromDb函数是这样的:

public object GetOrReadFromDb(string key)
{
    if(_cache[key]==null)
    {
        //Read stuff from DB, and insert into the cache
    }
    return _cache[key];
}

I think that it is possible, that control is taken away from the reading thread, and given to the clearing, for example right after reading from db, and before returning with the _chache[key] value, and that can cause problems. 我认为有可能将控制从读取线程中删除,并在清除之后,例如在从db读取之后,以及在返回_chache [key]值之前,这可能会导致问题。 Is it a real issue? 这是一个真正的问题吗?

The issue with your solution is that it introduces a possible race condition which would need ugly locks or other thread syncronization solutions. 您的解决方案的问题是它引入了可能的竞争条件,需要丑陋的锁或其他线程同步解决方案。

Instead, use the built-in solution. 相反,请使用内置解决方案。

You can use a custom ChangeMonitor class to clear your items from the cache. 您可以使用自定义ChangeMonitor类从缓存中清除项目。 You can add a ChangeMonitor instance to the ChangeMonitors property of the CacheItemPolicy you set when you call MemoryCache.Set . 您可以将ChangeMonitor实例添加到调用MemoryCache.Set时设置的CacheItemPolicyChangeMonitors属性中。

For example: 例如:

class MyCm : ChangeMonitor
{
    string uniqueId = Guid.NewGuid().ToString();

    public MyCm()
    {
        InitializationComplete();
    }

    protected override void Dispose(bool disposing) { }

    public override string UniqueId
    {
        get { return uniqueId; }
    }

    public void Stuff()
    {
        this.OnChanged(null);
    }
}

Usage example: 用法示例:

        var cache = new MemoryCache("MyFancyCache");
        var obj = new object();
        var cm = new MyCm();
        var cip = new CacheItemPolicy();
        cip.ChangeMonitors.Add(cm);

        cache.Set("MyFancyObj", obj, cip);

        var o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        cm.Stuff();

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

Never used the MemoryCache class, but there's an obvious race condition going on. 从未使用过MemoryCache类,但是有明显的竞争条件。 It's possible for a thread T1 to call GetElement (or whatever you call it) while another thread T2 is executing ClearCacheForAllUsers . 当另一个线程T2正在执行ClearCacheForAllUsers线程T1可以调用GetElement (或者你称之为的任何东西)。

Then the following can happen: 然后会发生以下情况:

T1:
reg = _cache
T2:
oldCache = _cache
_cache = new MemoryCache("MyCache")
oldCache.Dispose()
T1:
reg.Get()   // Ouch bad! Dispose tells us this results in "unexpected behavior"

The trivial solution would be to introduce synchronization. 简单的解决方案是引入同步。

If locking is unacceptable, you could use some clever tricks, but the easiest solution would be to use Finalize I'd say. 如果锁定是不可接受的,你可以使用一些聪明的技巧,但最简单的解决方案是使用Finalize我会说。 Sure, generally a bad idea, but assuming that the only thing the cache does is use up memory, so it doesn't hurt for the cache to only be collected under memory pressure you avoid having to worry about race conditions by letting the GC worry about it. 当然,通常是一个坏主意,但假设缓存所做的唯一事情就是耗尽内存,所以只有在内存压力下才能收集缓存而不会因为让GC担心而无需担心竞争条件关于它。 Note though that _cache has to be volatile. 请注意, _cache必须是易失性的。

Another possible approach is that as an implementation detail Get apparently always returns null after the cache is disposed. 另一种可能的方法是,作为一个实现细节, Get显然总是在释放缓存后返回null In that case you could avoid locking most of the times except in these rare situations. 在这种情况下,除了在极少数情况下,您可以避免大部分时间锁定。 Although this is implementation defined and I wouldn't like to rely on such a small detail personally. 虽然这是实现定义的,但我不想亲自依赖这么小的细节。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM