当前位置: 首页 > 科技观察

MemoryCache使用不当导致的BUG

时间:2023-03-17 13:20:38 科技观察

Intro前几天在代码中发现了一个BUG。原因是没有正确使用MemoryCache。可能很多人都知道,但我还是想分享记录一下,以免以后写同样的BUGSample。看看下面的例子awaitusingvarservices=newServiceCollection().AddMemoryCache().BuildServiceProvider();Console.WriteLine("-----Bad-----");GetValidValues(5).Dump();GetValidValues(8).Dump();ListGetValidValues(intthrehold){varmemoryCache=services.GetRequiredService();varvalues=memoryCache.GetOrCreate("test1",entry=>{returnEnumerable.Range(1,10).ToList();});values.RemoveAll(x=>x>threhold);returnvalues;}上面的Dump是一个扩展方法,输出列表中的元素,实现如下:publicstaticvoidDump(thisListvalues){varvalue=string.Join(",",values);Console.WriteLine(value);}好吧,让我们想想上面的输出会是什么。预期的结果应该是每次输出一个小于或等于输入的值。它究竟是什么样的?实际输出结果如下:Fix可以看到第二次输出的结果和我们预期的不一样。出现上述问题的原因是MemoryCache的对象是直接存放在内存中的。当缓存没有变化时,返回的是同一个对象。如果发生修改,后面会获取到修改后的状态,所以正确的做法应该是返回一个新的对象,而不是修改原来的对象。修改方法如下:ListGetValidValues(intthrehold){varmemoryCache=services.GetRequiredService();varvalues=memoryCache.GetOrCreate("test",entry=>{returnEnumerable.Range(1,10).ToList();});returnvalues.Where(v=>v<=threhold).ToList();}修改后输出如下:MoreMemoryCache后面其实是一个ConcurrentDictionary,value是一个对象CacheEntry,有过期时间。如果它没有过期或改变,它每次都返回相同的值。作为缓存对象的对象应该是只读的。不应修改缓存的对象。如果需要修改,则应创建一个新对象而不是原始对象。参考资料https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs#L26https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Caching.Memory/src/CacheEntry.cshttps://github.com/WeihanLi/SamplesInPractice/blob/master/MemoryCacheSample/Program.cs