在C#应用开发中,IMemoryCache是常用的内存缓存工具,但是它的原生过期机制无法主动触发自定义回调,也不支持自动定时清理过期缓存,需要开发者额外实现相关逻辑。下面介绍具体的实现方案。

IMemoryCache基础使用
首先我们需要了解IMemoryCache的基本用法,它位于Microsoft.Extensions.Caching.Memory命名空间下,需要先通过依赖注入注册服务。
// 注册IMemoryCache服务,在Program.cs中配置
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
var app = builder.Build();
// 注入使用IMemoryCache
public class CacheService
{
private readonly IMemoryCache _cache;
public CacheService(IMemoryCache cache)
{
_cache = cache;
}
// 添加缓存,设置绝对过期时间为10秒
public void AddCache(string key, string value)
{
_cache.Set(key, value, TimeSpan.FromSeconds(10));
}
// 获取缓存
public string GetCache(string key)
{
if (_cache.TryGetValue(key, out string value))
{
return value;
}
return null;
}
}
原生IMemoryCache的过期回调局限
IMemoryCache原生提供了CacheEntryExtensions中的RegisterPostEvictionCallback方法,可以在缓存项被移除时触发回调,但是这个回调存在限制:它只在缓存项被主动移除、过期后被访问触发过期移除、或者内存不足被回收时才会执行,不会在缓存过期瞬间自动触发,因此无法实现纯粹的定时过期回调。
// 原生过期移除回调示例
public void AddCacheWithCallback(string key, string value)
{
_cache.Set(key, value, new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(10))
// 注册移除回调
.RegisterPostEvictionCallback((key, value, reason, state) =>
{
Console.WriteLine($"缓存项{key}被移除,原因:{reason},值:{value}");
}));
}
方案一:定时轮询清理过期缓存
我们可以通过定时任务轮询所有缓存项,判断过期时间并触发清理逻辑,这种方式实现简单,适合缓存量不大的场景。
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;
public class PollingCacheCleaner : BackgroundService
{
private readonly IMemoryCache _cache;
private readonly ConcurrentDictionary<string, CacheItemInfo> _cacheKeyMap;
public PollingCacheCleaner(IMemoryCache cache)
{
_cache = cache;
_cacheKeyMap = new ConcurrentDictionary<string, CacheItemInfo>();
}
// 添加缓存时记录过期时间
public void AddCacheWithTrack(string key, string value, TimeSpan expiration)
{
var expirationTime = DateTime.Now.Add(expiration);
_cache.Set(key, value, expiration);
_cacheKeyMap[key] = new CacheItemInfo
{
Key = key,
ExpirationTime = expirationTime,
Value = value
};
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 每5秒轮询一次
await Task.Delay(5000, stoppingToken);
var now = DateTime.Now;
var expiredKeys = _cacheKeyMap
.Where(kv => kv.Value.ExpirationTime <= now)
.Select(kv => kv.Key)
.ToList();
foreach (var key in expiredKeys)
{
if (_cacheKeyMap.TryRemove(key, out var item))
{
_cache.Remove(key);
Console.WriteLine($"定时清理缓存:{key},值:{item.Value}");
}
}
}
}
}
// 缓存项信息类
public class CacheItemInfo
{
public string Key { get; set; }
public DateTime ExpirationTime { get; set; }
public string Value { get; set; }
}
需要在Program.cs中注册这个后台服务:
builder.Services.AddHostedService<PollingCacheCleaner>(); builder.Services.AddSingleton<PollingCacheCleaner>();
方案二:结合CancellationToken实现精准过期回调
我们可以利用CancellationTokenSource的定时取消功能,在缓存过期时触发回调,这种方式不需要轮询,回调触发更精准。
using Microsoft.Extensions.Caching.Memory;
using System.Threading;
public class CallbackCacheService
{
private readonly IMemoryCache _cache;
public CallbackCacheService(IMemoryCache cache)
{
_cache = cache;
}
public void AddCacheWithExpirationCallback(string key, string value, TimeSpan expiration, Action<string, string> callback)
{
// 创建取消令牌源,设置过期时间
var cts = new CancellationTokenSource(expiration);
// 注册取消回调,缓存过期时触发
cts.Token.Register(() =>
{
_cache.Remove(key);
callback?.Invoke(key, value);
});
// 设置缓存,同时把cts存入缓存关联项,避免被提前回收
_cache.Set(key, value, new MemoryCacheEntryOptions()
.AddExpirationToken(new CancellationChangeToken(cts.Token))
.RegisterPostEvictionCallback((k, v, r, s) =>
{
// 缓存移除时释放cts
((CancellationTokenSource)s).Dispose();
}, cts));
}
}
使用示例:
var cacheService = app.Services.GetRequiredService<CallbackCacheService>();
cacheService.AddCacheWithExpirationCallback(
"test_key",
"test_value",
TimeSpan.FromSeconds(10),
(key, value) =>
{
Console.WriteLine($"缓存{key}过期,值:{value},执行自定义清理逻辑");
});
两种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 定时轮询 | 实现简单,逻辑直观,无额外依赖 | 存在轮询间隔,回调不精准,大量缓存时性能一般 | 缓存量小,对过期时间精度要求不高的场景 |
| CancellationToken方案 | 回调触发精准,无轮询开销,性能更好 | 实现稍复杂,需要管理CancellationTokenSource生命周期 | 缓存量大,对过期回调精度要求高的场景 |
注意事项
- 使用定时轮询方案时,轮询间隔需要根据实际业务调整,避免间隔太短造成性能浪费,间隔太长导致过期缓存清理不及时。
- 使用CancellationToken方案时,一定要注意释放CancellationTokenSource资源,避免内存泄漏。
- 如果应用需要持久化缓存或者分布式缓存,IMemoryCache不再适用,需要替换为Redis等分布式缓存组件,对应的过期回调实现逻辑也会有所调整。
C#IMemoryCache定时清理缓存过期回调修改时间:2026-06-10 22:39:19