如何在ASP.NET Core中从IMemoryCache中删除所有对象(重置)

本文关键字:删除 对象 重置 IMemoryCache ASP NET 中从 Core | 更新日期: 2023-09-27 18:27:37

有一个Remove方法可以通过其键从IMemoryCache中删除对象。有没有办法重置整个缓存并删除所有对象?

使用"如何清除MemoryCache?"中所述的Dispose方法?不起作用:

ObjectDisposedException: Cannot access a disposed object.
 Object name: 'Microsoft.Extensions.Caching.Memory.MemoryCache'.

如何在ASP.NET Core中从IMemoryCache中删除所有对象(重置)

请参阅ASP.NET Core中的内存缓存,特别是关于缓存依赖项。

使用CancellationTokenSource可以将多个缓存条目作为一个组逐出

这个代码对我有效:

public class CacheProvider 
{
    private static CancellationTokenSource _resetCacheToken = new CancellationTokenSource();
    private readonly IMemoryCache _innerCache;
    /* other methods and constructor removed for brevity */
    public T Set<T>(object key, T value) 
    {
        /* some other code removed for brevity */
        var options = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Normal).SetAbsoluteExpiration(typeExpiration);
        options.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token));
        _innerCache.Set(CreateKey(type, key), value, options);
        return value;
    }
    public void Reset()
    {
        if (_resetCacheToken != null && !_resetCacheToken.IsCancellationRequested && _resetCacheToken.Token.CanBeCanceled)
        {
            _resetCacheToken.Cancel();
            _resetCacheToken.Dispose();
        }
        _resetCacheToken = new CancellationTokenSource();
    }
}

UPDATE:在.NET7或更新版本中,只需调用新的Clear方法。您可以按以下方式投射IMemoryCache:

// somewhere else, maybe a class variable injected via constructor
IMemoryCache memoryCacheInterface;
// later, if you need to clear, do this
if (memoryCacheInterface is MemoryCache concreteMemoryCache)
{
    concreteMemoryCache.Clear();
}

对于较旧的.NET版本,请阅读…

最简单的方法是Compact(1.0)(如果可用的话)。否则,这里有一些代码将使用扩展方法(在单元测试和.NET核心2.2和3.1上的生产中测试)清除内存缓存。如果Compact不可用,则使用回退方法,从公共Clear方法开始,然后是内部Clear方法。如果这些都不可用,则抛出异常。

/// <summary>
/// Clear IMemoryCache
/// </summary>
/// <param name="cache">Cache</param>
/// <exception cref="InvalidOperationException">Unable to clear memory cache</exception>
/// <exception cref="ArgumentNullException">Cache is null</exception>
public static void Clear(this IMemoryCache cache)
{
    if (cache == null)
    {
        throw new ArgumentNullException("Memory cache must not be null");
    }
    else if (cache is MemoryCache memCache)
    {
        memCache.Compact(1.0);
        return;
    }
    else
    {
        MethodInfo clearMethod = cache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
        if (clearMethod != null)
        {
            clearMethod.Invoke(cache, null);
            return;
        }
        else
        {
            PropertyInfo prop = cache.GetType().GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
            if (prop != null)
            {
                object innerCache = prop.GetValue(cache);
                if (innerCache != null)
                {
                    clearMethod = innerCache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
                    if (clearMethod != null)
                    {
                        clearMethod.Invoke(innerCache, null);
                        return;
                    }
                }
            }
        }
    }
    throw new InvalidOperationException("Unable to clear memory cache instance of type " + cache.GetType().FullName);
}

如果使用标准MemoryCache,此代码会有所帮助。文件:https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1#内存缓存压缩

_cache.Compact(1.0);

为了重置IMemoryCache对象,我创建了一个方法,使用GetType()中的GetProperty()访问EntriesCollection属性。有了这个集合,我使用GetType()的GetMethod()来调用Clear()方法。

保持原样:

public void IMemoryCacheClear(IMemoryCache memoryCache)
{
    PropertyInfo prop = memoryCache.GetType().GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
    if (prop is null)
        return;
    object innerCache = prop.GetValue(memoryCache);
    MethodInfo clearMethod = innerCache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
    clearMethod.Invoke(innerCache, null);
}

目前我迁移到了.NET7,我需要更正该方法,因为EntriesCollection不再返回集合数据。Clear()方法现在存在于.NET7的MemoryCache类中。因此,要修复它,只需将变量转换为MemoryCache并调用Clear()方法。

修正如下:

public void IMemoryCacheClear(IMemoryCache memoryCache)
{
    if (memoryCache is MemoryCache cache)
    {
        cache.Clear();    
    }
}

希望这能有所帮助!

我的解决方案是创建一个包装器,重新公开现有的几个方法,并通过用全新的方法替换MemoryCache对象来添加缺失的方法。工作对我来说很好。代码如下:

public interface IMyMemoryCache : IMemoryCache
{
    void Reset();
}
public class MyMemoryCache: IMyMemoryCache
{
    IMemoryCache _memoryCache;
    public MyMemoryCache()
    {
        Reset();
    }
    public void Dispose()
    {
        _memoryCache.Dispose();
    }
    public bool TryGetValue(object key, out object value)
    {
        return _memoryCache.TryGetValue(key, out value);
    }
    public ICacheEntry CreateEntry(object key)
    {
        return _memoryCache.CreateEntry(key);
    }
    public void Remove(object key)
    {
        _memoryCache.Remove(key);
    }
    public void Reset()
    {
        var existingCache = _memoryCache;
        _memoryCache = new MemoryCache(new MemoryCacheOptions());
        // Dispose existing cache (we override) in 10 minutes
        if (existingCache != null)
        {
            System.Threading.Tasks.Task.Delay(TimeSpan.FromMinutes(10))
                .ContinueWith(t =>
                {
                    existingCache.Dispose();
                });
        }
    }
}

RC1的答案是,根据我所读到的和被告知的内容,你不能开箱即用(我确实在GitHub上读到,可能有一种方法可以创建触发器来促进即将到来的事情)。

目前,为您提供了Get、Set和Remove。我认为你的选择是:

  1. 创建一个将跟踪所有密钥的缓存管理器包装,然后可以根据需要批量删除这些项。我不喜欢这个,但它会起作用的。当然,如果你不是控制添加的人,那么缓存中可能有你不知道的东西(你可以将你的计数与它的计数进行比较)。如果将IMemoryCache强制转换为MemoryCache,则可以获得已公开的Count属性
  2. 分叉程序集并暴露密钥和/或添加删除这些项的方法。有一个底层字典保存这些键。我做了这件事,编译了它,为它创建了一个Nuget包,然后替换了RC1版本,只是想看看我是否可以(它起作用了)。不确定这是否是正确的方式,但这是对我的fork的承诺,我只是添加了一个只读属性,在那里我将键转储到对象列表中(键存储为对象)。与过去的MemoryCache实现一样,如果您暴露了密钥,它们在转储后可能会过时,但如果您只是使用它们来清除所有密钥,那么这应该无关紧要

https://github.com/blakepell/Caching/commit/165ae5ec13cc51c44a6007d6b88bd9c567e1d724

我昨晚发布了这个问题,试图弄清楚是否有一个好的方法来具体检查缓存中的内容(问我们为什么没有方法)。如果你不问,你永远不会知道这是否重要,所以我想为什么不呢。

https://github.com/aspnet/Caching/issues/149

我的解决方案是将缓存中所有项目的新截止日期设置为1毫秒。然后它们过期,因此进行缓存刷新。

不要使用紧凑的解决方案,它就是不起作用。

我在整个项目中都使用IMemoryCache。在一个特定的时间,我有大约42k个条目。调用Compact(1.0)后,仍有14k剩余。

这里似乎描述了唯一的工作方法:如何在asp.net核心中检索内存缓存键的列表?

为了适应这个问题,我最终使用了它,如下所示:

public static class MemoryCacheExtensions
{
    private static readonly Func<MemoryCache, object> GetEntriesCollection = Delegate.CreateDelegate(
        typeof(Func<MemoryCache, object>),
        typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true),
        throwOnBindFailure: true) as Func<MemoryCache, object>;
    public static IEnumerable GetKeys(this IMemoryCache memoryCache) =>
        ((IDictionary)GetEntriesCollection((MemoryCache)memoryCache)).Keys;
    public static IEnumerable<T> GetKeys<T>(this IMemoryCache memoryCache) =>
        GetKeys(memoryCache).OfType<T>();
    public static void Clear(this IMemoryCache memoryCache) => ((IDictionary)GetEntriesCollection((MemoryCache)memoryCache)).Clear();
}

别忘了对相关答案投赞成票。

我通过在IMemoryCache周围创建一个FlushableMemoryCache单例来解决这个问题,该单例跟踪当前存储在缓存中的密钥,然后可以对它们进行迭代以刷新所有密钥:

public interface IFlushableMemoryCache
{
    void Set<T>(string cacheId, object key, T value);
    bool TryGetValue<T>(object key, out T value);
    void Remove(string cacheId, object key);
    void Flush(string cacheId);
}

public class FlushableMemoryCache : IFlushableMemoryCache
{
    private readonly IMemoryCache _memoryCache;
    private readonly ConcurrentDictionary<string, HashSet<object>> _keyDictionary;
    public FlushableMemoryCache(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
        _keyDictionary = new ConcurrentDictionary<string, HashSet<object>>();
    }

    public void Set<T>(string cacheId, object key, T value)
    {
        _memoryCache.Set(key, value);
        _keyDictionary.AddOrUpdate(cacheId, new HashSet<object>(new[] {key}),
            (id, oldVal) =>
            {
                oldVal.Add(key);
                return oldVal;
            });
    }
    public bool TryGetValue<T>(object key, out T value)
    {
        return _memoryCache.TryGetValue(key, out value);
    }
    public void Remove(string cacheId, object key)
    {
        _memoryCache.Remove(key);
        if (_keyDictionary.ContainsKey(cacheId) && _keyDictionary[cacheId].Contains(key))
        {
            _keyDictionary[cacheId].Remove(key);
        }
    }
    public void Flush(string cacheId)
    {
        foreach (var key in _keyDictionary[cacheId])
        {
            _memoryCache.Remove(key);
        }
        _keyDictionary[cacheId] = new HashSet<object>();
    }
}

利用这一点的服务将需要提供对该服务唯一的CCD_ 11。这允许Flush只清除与特定服务相关的密钥,而不是清除缓存中的所有密钥!

  IMemoryCache _MemoryCache;
    public CacheManager(IMemoryCache MemoryCache)
    {
        _MemoryCache = MemoryCache;
    }
    public void Clear()
    {
        _MemoryCache.Dispose();
        _MemoryCache = new MemoryCache(new MemoryCacheOptions());
    }