如何清除MemoryCache?


100

我已经使用MemoryCache类创建了一个缓存。我向其中添加了一些项目,但是当我需要重新加载缓存时,我想先清除它。最快的方法是什么?我应该遍历所有项目并一次删除一个项目,还是有更好的方法?


1
对于.NET core,请检查答案。
玛卡拉

Answers:


61

Dispose 现有的MemoryCache并创建一个新的MemoryCache对象。


3
我最初使用MemoryCache.Default,导致Dispose给我带来一些麻烦。尽管如此,Dispose最终还是我能找到的最佳解决方案。谢谢。
LaustN

11
@LaustN您能详细说明由MemoryCache.Default引起的“麻烦”吗?我当前正在使用MemoryCache.Default ... MSDN的MemoryCache文档使我想知道是否建议处置和重新创建:“除非需要,否则不要创建MemoryCache实例。如果在客户端和Web应用程序中创建缓存实例,则MemoryCache实例应在应用程序生命周期的早期创建”。这适用于.Default吗?我并不是说使用Dispose是错误的,老实说,我只是想澄清所有这些。
ElonU Webdev 2011年

8
以为值得一提的是,Dispose 它确实调用了任何CacheEntryRemovedCallback附加到当前缓存项的附件。
Mike Guthrie

8
@ElonU:以下堆栈溢出答案说明了处理默认实例时可能会遇到的一些麻烦:stackoverflow.com/a/8043556/216440。引用:“缓存的状态设置为指示缓存已被处置。任何尝试调用更改缓存状态的公共缓存方法(例如添加,删除或检索缓存条目的方法)可能会导致意外情况例如,如果在处置缓存后调用Set方法,则会发生无操作错误。”
西蒙·图西

56

枚举问题

MemoryCache.GetEnumerator()备注节警告:“为MemoryCache实例检索枚举器是资源密集型且阻塞操作。因此,不应在生产应用程序中使用该枚举器。”

是在GetEnumerator()实现的伪代码中解释的原因

Create a new Dictionary object (let's call it AllCache)
For Each per-processor segment in the cache (one Dictionary object per processor)
{
    Lock the segment/Dictionary (using lock construct)
    Iterate through the segment/Dictionary and add each name/value pair one-by-one
       to the AllCache Dictionary (using references to the original MemoryCacheKey
       and MemoryCacheEntry objects)
}
Create and return an enumerator on the AllCache Dictionary

由于该实现将缓存划分为多个Dictionary对象,因此它必须将所有内容放到一个集合中,以递归枚举器。每次调用GetEnumerator都会执行上面详述的完整复制过程。新创建的字典包含对原始内部键和值对象的引用,因此您的实际缓存数据值不会重复。

文档中的警告是正确的。避免使用GetEnumerator()-包括上面所有使用LINQ查询的答案。

更好,更灵活的解决方案

这是清除缓存的有效方法,该缓存仅建立在现有变更监视基础结构上。它还提供了清除整个缓存或仅清除命名子集的灵活性,并且没有上述任何问题。

// By Thomas F. Abraham (http://www.tfabraham.com)
namespace CacheTest
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Caching;

    public class SignaledChangeEventArgs : EventArgs
    {
        public string Name { get; private set; }
        public SignaledChangeEventArgs(string name = null) { this.Name = name; }
    }

    /// <summary>
    /// Cache change monitor that allows an app to fire a change notification
    /// to all associated cache items.
    /// </summary>
    public class SignaledChangeMonitor : ChangeMonitor
    {
        // Shared across all SignaledChangeMonitors in the AppDomain
        private static event EventHandler<SignaledChangeEventArgs> Signaled;

        private string _name;
        private string _uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);

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

        public SignaledChangeMonitor(string name = null)
        {
            _name = name;
            // Register instance with the shared event
            SignaledChangeMonitor.Signaled += OnSignalRaised;
            base.InitializationComplete();
        }

        public static void Signal(string name = null)
        {
            if (Signaled != null)
            {
                // Raise shared event to notify all subscribers
                Signaled(null, new SignaledChangeEventArgs(name));
            }
        }

        protected override void Dispose(bool disposing)
        {
            SignaledChangeMonitor.Signaled -= OnSignalRaised;
        }

        private void OnSignalRaised(object sender, SignaledChangeEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(e.Name) || string.Compare(e.Name, _name, true) == 0)
            {
                Debug.WriteLine(
                    _uniqueId + " notifying cache of change.", "SignaledChangeMonitor");
                // Cache objects are obligated to remove entry upon change notification.
                base.OnChanged(null);
            }
        }
    }

    public static class CacheTester
    {
        public static void TestCache()
        {
            MemoryCache cache = MemoryCache.Default;

            // Add data to cache
            for (int idx = 0; idx < 50; idx++)
            {
                cache.Add("Key" + idx.ToString(), "Value" + idx.ToString(), GetPolicy(idx));
            }

            // Flush cached items associated with "NamedData" change monitors
            SignaledChangeMonitor.Signal("NamedData");

            // Flush all cached items
            SignaledChangeMonitor.Signal();
        }

        private static CacheItemPolicy GetPolicy(int idx)
        {
            string name = (idx % 2 == 0) ? null : "NamedData";

            CacheItemPolicy cip = new CacheItemPolicy();
            cip.AbsoluteExpiration = System.DateTimeOffset.UtcNow.AddHours(1);
            cip.ChangeMonitors.Add(new SignaledChangeMonitor(name));
            return cip;
        }
    }
}

8
似乎是缺少Region功能的实现。
Jowen

非常好。我一直在尝试使用链接的内存缓存监视器和向导来实现某些功能,但是当我尝试加强功能时,它开始变得有些丑陋。
2014年

7
我不建议将此模式用于一般用途。1.它的速度很慢,没有实现上的错误,但是dispose方法非常慢。2.如果您从缓存中逐出的项目已过期,则仍会调用“更改”监视器。3.我的机器吞没了所有CPU,并且在运行性能测试时花了很长时间才能从缓存中清除30k项。等待5分钟以上之后,我几次取消了测试。
亚伦M

1
@PascalMathys不幸的是,没有比这更好的解决方案了。尽管有缺点,但最终还是使用了它,因为它仍然比使用枚举更好。
亚伦M

9
@AaronM这种解决方案是否还比仅仅处理缓存和实例化一个新的缓存更好?
RobSiklos

35

http://connect.microsoft.com/VisualStudio/feedback/details/723620/memorycache-class-needs-a-clear-method

解决方法是:

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

33
文档中检索MemoryCache实例的枚举器是资源密集型且阻塞操作。因此,该枚举器不应在生产应用程序中使用。
TrueWill 2012年

3
@emberdude与检索枚举数完全相同-您对实现的看法是什么Select()
RobSiklos

1
就个人而言,我在单元测试[TestInitialize]函数中使用它来清除每个单元测试的内存缓存。否则,在尝试比较两个函数之间的性能时,跨单元测试的缓存将持续存在,从而产生意外结果。
雅各布·莫里森

6
@JacobMorrison可以说,单元测试不是“生产应用程序” :)
Mels

1
@Mels可以说,单元测试应该按照与“生产应用程序”相同的标准编写!:)
Etherman


10

如果性能不是问题,那么这个不错的单线解决方案可以解决这个问题:

cache.ToList().ForEach(a => cache.Remove(a.Key));


3

您还可以执行以下操作:


Dim _Qry = (From n In CacheObject.AsParallel()
           Select n).ToList()
For Each i In _Qry
    CacheObject.Remove(i.Key)
Next

3

以此为基础,编写了一种稍微有效的并行清除方法:

    public void ClearAll()
    {
        var allKeys = _cache.Select(o => o.Key);
        Parallel.ForEach(allKeys, key => _cache.Remove(key));
    }

1
您是否测试过它是否更快(或更慢)?
保罗乔治,

1

使用c#GlobalCachingProvider时,我只对清除缓存感兴趣,并发现它是一个选项

                var cache = GlobalCachingProvider.Instance.GetAllItems();
                if (dbOperation.SuccessLoadingAllCacheToDB(cache))
                {
                    cache.Clear();
                }

0

magritte答案的改进版本。

var cacheKeys = MemoryCache.Default.Where(kvp.Value is MyType).Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
    MemoryCache.Default.Remove(cacheKey);
}

0

您可以处理MemoryCache.Default缓存,然后将私有字段单例重置为null,以使其重新创建MemoryCache.Default。

       var field = typeof(MemoryCache).GetField("s_defaultCache",
            BindingFlags.Static |
            BindingFlags.NonPublic);
        field.SetValue(null, null);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.