淺談 MemoryCache 的原生插值方式
.NET運行時內(nèi)置了常用的緩存模塊:MemoryCache
標準的MemoryCache暴露了如下幾個屬性和方法:
- public int Count { get; }
- public void Compact(double percentage);
- public ICacheEntry CreateEntry(object key);
- public void Dispose();
- public void Remove(object key);
- public bool TryGetValue(object key, out object result);
- protected virtual void Dispose(bool disposing);
但是你使用常規(guī)模式去插值/獲取值,可能會出現(xiàn)意想不到的情況。
就如下這樣的常規(guī)代碼:
- var s = new MemoryCache(new MemoryCacheOptions { });
- var entry = s.CreateEntry("WeChatID");
- entry.Value = "精益碼農(nóng)";
- var f = s.TryGetValue("WeChatID",out object obj);
- Console.WriteLine(f);
- Console.WriteLine(obj);
會輸出如下結(jié)果:
是不是很意外。
但是看官們一般不會使用MemoryCache的原生方法,而是使用位于同一命名空間的 擴展方法Set。
- var s = new MemoryCache(new MemoryCacheOptions { });
- s.Set("WeChatID", "精益碼農(nóng)");
- var f = s.TryGetValue("WeChatID", out object obj);
- Console.WriteLine(f);
- Console.WriteLine(obj);
如此便能正確輸出。
擴展類源碼看一看
- public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value)
- {
- using ICacheEntry entry = cache.CreateEntry(key);
- entry.Value = value;
- return value;
擴展方法與原生方法的差異在于using關(guān)鍵字 (也說明了CacheEntry繼承自IDisposable接口)。
繼續(xù)追溯CacheEntry實現(xiàn)的Dispose方法:
- public void Dispose()
- {
- if (!_state.IsDisposed)
- {
- _state.IsDisposed = true;
- if (_cache.TrackLinkedCacheEntries)
- {
- CacheEntryHelper.ExitScope(this, _previous);
- }
- // Don't commit or propagate options if the CacheEntry Value was never set.
- // We assume an exception occurred causing the caller to not set the Value successfully,
- // so don't use this entry.
- if (_state.IsValueSet)
- {
- _cache.SetEntry(this);
- if (_previous != null && CanPropagateOptions())
- {
- PropagateOptions(_previous);
- }
- }
- _previous = null; // we don't want to root unnecessary objects
- }
- }
注意其中的_cache.SetEntry(this),表示在MemoryCache底層的ConcurrentDictionary
綜上:緩存項CacheEntry需要被Dispose,才能被插入MemoeyCache。
這是怎樣的設(shè)計模式?IDisposable接口不是用來釋放資源嗎?
為啥要使用Dispose方法來向MemoryCache插值?
不能使用一個明確的Commit方法嗎?
這在Github上也有issue討論,從2017年開始就有大佬質(zhì)疑這是一個反人類的設(shè)計思路,官方為了不引入Break Change,一直保持到現(xiàn)在。
基于此現(xiàn)狀,我們?nèi)绻褂肕emoryCache的原生插值方法, 需要這樣:
- var s = new MemoryCache(new MemoryCacheOptions { });
- using (var entry = s.CreateEntry("WeChatID"))
- {
- entry.Value = "精益碼農(nóng)";
- }
- var f = s.TryGetValue("WeChatID", out object obj);
- ...
盡量不要使用C#8.0推出的不帶大括號的using語法
- using var entry = s.CreateEntry("WeChatID");
- entry.Value = "精益碼農(nóng)";
- var f = s.TryGetValue("WeChatID", out object obj);
- ...
這種沒明確指定using作用范圍的語法,會在函數(shù)末尾才執(zhí)行Dispose方法, 導(dǎo)致執(zhí)行到TryGetValue時,緩存項其實還沒插入!!!
Last
- MemoryCache插值的實現(xiàn)過程很奇葩
- 盡量使用帶明確大括號范圍的using語法,C#8.0推出的不帶大括號的using語法糖的作用時刻在函數(shù)末尾,會帶來誤導(dǎo)。