别再只会用TryGetValue了!ASP.NET Core IMemoryCache的GetOrCreateAsync才是懒人福音
别再只会用TryGetValue了ASP.NET Core IMemoryCache的GetOrCreateAsync才是懒人福音在.NET开发中缓存是提升应用性能的利器但很多开发者仍在使用繁琐的TryGetValueSet组合。这种写法不仅冗长还容易遗漏线程安全和缓存策略配置。GetOrCreateAsync方法正是为解决这些问题而生的一站式解决方案。1. 为什么GetOrCreateAsync是更好的选择传统缓存操作通常需要先检查键是否存在再决定是否创建新值。这种模式会导致代码重复且容易出错。例如// 传统写法 if (!_cache.TryGetValue(myKey, out var cachedValue)) { cachedValue await ExpensiveOperationAsync(); _cache.Set(myKey, cachedValue, new MemoryCacheEntryOptions { SlidingExpiration TimeSpan.FromMinutes(5) }); }GetOrCreateAsync将这些操作封装为一个原子操作var result await _cache.GetOrCreateAsync(myKey, async entry { entry.SlidingExpiration TimeSpan.FromMinutes(5); return await ExpensiveOperationAsync(); });这种方法有三大优势代码简洁将检查、创建和配置合并为一个操作线程安全内部实现了锁机制防止并发问题配置直观缓存选项直接在创建时设置2. GetOrCreateAsync的高级用法2.1 灵活的过期策略组合GetOrCreateAsync支持多种过期策略的灵活组合var result await _cache.GetOrCreateAsync(compositeKey, async entry { // 滑动过期时间 entry.SlidingExpiration TimeSpan.FromMinutes(10); // 绝对过期时间 entry.AbsoluteExpirationRelativeToNow TimeSpan.FromHours(1); // 优先级设置 entry.Priority CacheItemPriority.High; return await ComplexQueryAsync(); });提示同时设置滑动和绝对过期时以先到达的为准。这种组合策略特别适合既要防止长期不访问的数据占用内存又要确保数据不会无限期缓存的情况。2.2 依赖项与回调机制通过GetOrCreateAsync可以轻松实现缓存依赖和失效回调var cts new CancellationTokenSource(TimeSpan.FromMinutes(30)); var data await _cache.GetOrCreateAsync(dataWithDependency, async entry { entry.AddExpirationToken(new CancellationChangeToken(cts.Token)) .RegisterPostEvictionCallback((key, value, reason, state) { Logger.LogInformation($缓存 {key} 已失效原因: {reason}); }); return await FetchDataWithDependenciesAsync(); });3. 性能考量与最佳实践3.1 内存使用优化当应用需要控制缓存大小时GetOrCreateAsync同样适用配置项说明示例值SizeLimit总缓存大小限制1000CompactionPercentage内存压力时压缩比例0.2ExpirationScanFrequency过期扫描频率TimeSpan.FromMinutes(5)services.AddMemoryCache(options { options.SizeLimit 1000; options.CompactionPercentage 0.2; }); // 使用时的缓存项大小设置 var report await _cache.GetOrCreateAsync(largeReport, async entry { entry.SetSize(1) // 每个缓存项占用单位 .SetSlidingExpiration(TimeSpan.FromHours(1)); return await GenerateLargeReportAsync(); });3.2 避免的常见陷阱过度缓存不是所有数据都适合缓存频繁变化的业务数据可能造成缓存击穿大对象缓存大对象会增加GC压力考虑分块或使用分布式缓存缓存雪崩为不同键设置随机化的过期时间偏移量// 良好的随机过期时间实践 var random new Random(); var result await _cache.GetOrCreateAsync(criticalData, async entry { var baseExpiration TimeSpan.FromMinutes(30); var jitter TimeSpan.FromSeconds(random.Next(0, 300)); // 0-5分钟随机抖动 entry.AbsoluteExpirationRelativeToNow baseExpiration jitter; return await GetCriticalDataAsync(); });4. 与其他缓存API的对比了解何时使用哪种API能显著提升代码质量方法适用场景线程安全异步支持代码简洁度Get简单读取不关心是否存在是否★★★★☆TryGetValue需要检查存在的同步场景是否★★☆☆☆GetOrCreate同步创建场景是否★★★★☆GetOrCreateAsync异步数据加载是是★★★★★在实际项目中我逐渐将大部分TryGetValueSet模式替换为GetOrCreateAsync不仅减少了约40%的缓存相关代码量还消除了多个因并发导致的微妙bug。特别是在处理数据库查询结果缓存时其异步支持让代码保持了良好的可读性。