【问题标题】:How do I clear a System.Runtime.Caching.MemoryCache如何清除 System.Runtime.Caching.MemoryCache
【发布时间】:2011-12-24 00:14:11
【问题描述】:

我使用System.Runtime.Caching.MemoryCache 来保存永不过期的物品。但是,有时我需要清除整个缓存的能力。我该怎么做?

我问了一个类似的问题here,关于我是否可以枚举缓存,但这是一个坏主意,因为它需要在枚举期间同步。

我尝试过使用.Trim(100),但这根本不起作用。

我尝试通过 Linq 获取所有键的列表,但后来我又回到了开始的地方,因为逐个逐出项目很容易导致竞争条件。

我想存储所有密钥,然后为每个密钥发出一个.Remove(key),但那里也有一个隐含的竞争条件,所以我需要锁定对密钥列表的访问,事情变得一团糟再次。

然后我认为我应该能够在整个缓存上调用.Dispose(),但由于它的实现方式,我不确定这是否是最好的方法。

使用ChangeMonitors 不是我的设计选项,而且对于这样一个微不足道的要求来说过于复杂。

那么,如何彻底清除缓存呢?

【问题讨论】:

  • MSDN 明确声明它不是单例:“另一个区别是您可以创建 MemoryCache 类的多个实例,以便在同一个应用程序和同一个 AppDomain 中使用实例。”
  • @MarcGravell 啊!我可能正在查看错误的文档。所以这似乎意味着我可以打电话给.Dispose(),然后当我打电话给MemoryCache.Default时,我会得到一个全新的实例......
  • 只是简单地交换到一个新实例并处理旧的问题?假设同步等
  • 否,如果您在默认实例上调用 dispose,则您已经杀死了默认实例。所以...如果您打算使用 Dispose,请不要使用 .Default!不过,这并不能使它成为单例...
  • 我现在才发现有一个similar question。但围绕Dispose() 的讨论并不完整。

标签: c# asp.net caching .net-4.0


【解决方案1】:

一开始我很挣扎。 MemoryCache.Default.Trim(100) 不起作用(如上所述)。 Trim 是最好的尝试,因此如果缓存中有 100 个项目,并且您调用 Trim(100) 它将删除最少使用的项目。

Trim 返回删除项目的数量,大多数人希望删除所有项目。

此代码在我使用 MemoryCache.Default 的 xUnit 测试中为我从 MemoryCache 中删除所有项目。 MemoryCache.Default 是默认区域。

foreach (var element in MemoryCache.Default)
{
    MemoryCache.Default.Remove(element.Key);
}

【讨论】:

  • 需要明确的是,Trim 采用 百分比,而不是绝对计数,因此 Trim(100) 表示 100% 的项目 - 而不是 100 个项目 - 应该被删除。
  • 你绝对应该在这里添加线程同步。很惊讶您没有收到“在枚举时修改此集合”异常。
  • 由于MemoryCache implements GetEnumerator() 的方式,此用法不应引发IllegalOperationException。它通过从其内部存储复制数据来创建一个新的Dictionary<string, object>,并为该新对象返回一个枚举器,因此 MemoryCache 本身不会同时被枚举和修改。
  • 我在我的 TestInitialize 中使用这种方法进行单元测试。我建议在生产代码中避免这种情况!
  • MS 文档指出:“检索 MemoryCache 实例的枚举器是一项资源密集型和阻塞操作。因此,不应在生产应用程序中使用枚举器。”
【解决方案2】:

如果您希望能够再使用 MemoryCache 的 Default 成员,则不应调用 dispose:

缓存的状态被设置为表示缓存被释放。 任何尝试调用更改状态的公共缓存方法 缓存,例如添加、删除或检索缓存的方法 条目,可能会导致意外行为。例如,如果您调用 缓存释放后的set方法,出现no-op错误。如果你 尝试从缓存中检索项目,Get 方法将始终 什么都没有。 http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache.dispose.aspx

关于修剪,它应该可以工作:

Trim 属性首先删除已超过绝对或滑动过期的条目。任何注册的回调 对于已删除的项目,将传递已过期的已删除原因。

如果删除过期条目不足以达到指定的修剪百分比,则会从缓存中删除其他条目 基于最近最少使用 (LRU) 算法,直到请求 已达到修剪百分比。

但是另外两个用户报告它在同一页面上不起作用,所以我猜你被 Remove() http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache.trim.aspx卡住了

更新 但是,我没有提到它是单例的,或者有多个实例是不安全的,所以你应该能够覆盖你的引用。

但如果您需要从 Default 实例中释放内存,则必须手动清除它或通过 dispose 永久销毁它(使其无法使用)。

根据您的问题,您可以创建自己的单例类,返回一个您可以在内部随意处置的 Memorycache。作为缓存的性质 :-)

【讨论】:

  • 所以我可以包装一个我创建的实例——即不是 Default 实例——在一个适配器类中,它是一个单例。然后,当我想清除包含的缓存时,我只需创建一个新缓存,然后鞭打 GC。
  • @Peter Marks 我看不出它为什么会失败。而且 Dispose() 通常应该确实可以解决问题,但是如果调用 GC.Collect() 的大量数据可能是个好主意。
  • 不幸的是,Trim(100) 并不总是 100% 驱逐缓存中的项目:connect.microsoft.com/visualstudio/feedback/details/831755/…
  • 我对 Trim 也没有运气,所以这里有一个如何使用 remove 的示例:stackoverflow.com/a/7334092/1641941
  • 清理构建过程会清除本地 iis 或 iis express 的缓存吗?对于不在本地 iis 上的托管站点,缓存的生命周期是多少?直到应用程序池被回收是永远的吗?
【解决方案3】:

这是我为我正在做的事情所做的……

public void Flush()
{
    List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
    foreach (string cacheKey in cacheKeys)
    {
        MemoryCache.Default.Remove(cacheKey);
    }
}

【讨论】:

    【解决方案4】:

    我知道这是一个老问题,但我遇到的最佳选择是

    释放现有的 MemoryCache 并创建一个新的 MemoryCache 对象。 https://stackoverflow.com/a/4183319/880642

    答案并没有真正提供以线程安全的方式执行此操作的代码。但这可以使用Interlocked.Exchange来实现

    var oldCache = Interlocked.Exchange(ref _existingCache, new MemoryCache("newCacheName"));
    oldCache.Dispose();
    

    这会将现有缓存与新缓存交换,并允许您在原始缓存上安全地调用 Dispose。这避免了需要枚举缓存中的项目和由于在使用缓存时释放缓存而导致的竞争条件。

    【讨论】:

    • 交换不够 IMO:如何保护并发读者免受处置?我认为您需要一个“多读单写”锁才能真正安全。
    【解决方案5】:

    @stefan's answer中的详细信息详细说明了原理;这就是我的做法。

    应该在重新创建缓存时同步对缓存的访问,以避免客户端代码在缓存被释放后、重新创建之前访问缓存的竞争条件。

    为避免这种同步,请在您的适配器类(包装 MemoryCache)中执行此操作:

    public void clearCache() {
      var oldCache = TheCache;
      TheCache = new MemoryCache("NewCacheName", ...);
      oldCache.Dispose();
      GC.Collect();
    }
    

    这样TheCache就一直处于未释放状态,不需要同步。

    【讨论】:

    • 真的吗?一个成熟的 GC.Collect?
    • 这个解决方案绝对不能避免规定的竞争条件。考虑线程 A 获得对TheCache 的引用并开始对其进行操作。线程 B 调用clearCache,运行完成。线程 A 仍在运行,但它正在使用已释放的缓存。
    【解决方案6】:

    我也遇到了这个问题。 .Dispose() 做了一些与我预期完全不同的事情。

    相反,我在控制器类中添加了一个静态字段。我没有使用默认缓存来解决此问题,而是创建了一个私有缓存(如果您想这样称呼它)。所以我的实现看起来有点像这样:

    public class MyController : Controller
    {
    
        static MemoryCache s_cache = new MemoryCache("myCache");
    
        public ActionResult Index()
        {
    
            if (conditionThatInvalidatesCache)
            {
                s_cache = new MemoryCache("myCache");
            }
    
            String s = s_cache["key"] as String;
    
            if (s == null)
            {
                //do work
                //add to s_cache["key"]
            }
    
            //do whatever next
        }
    }
    

    【讨论】:

    • MemoryCache 实现了 IDisposable,因此您应该在替换旧实例之前调用 Dispose。
    【解决方案7】:

    查看this 的帖子,特别是Thomas F. Abraham 发布的答案。 它有一个解决方案,使您能够清除整个缓存或命名的子集。

    这里的关键是:

    // Cache objects are obligated to remove entry upon change notification.
    base.OnChanged(null);
    

    我自己实现了这个,一切似乎都很好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 2011-01-16
      • 2010-11-07
      • 2011-08-16
      相关资源
      最近更新 更多