• 浅谈MemoryCache的原生插值方式


    .NET运行时内置了常用的缓存模块: 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);
    

    但是你使用常规模式去插值/获取值,可能会出现意想不到的情况。

    就如下这样的常规代码:

    var s = new MemoryCache(new MemoryCacheOptions { });
    var entry = s.CreateEntry("WeChatID");
    entry.Value = "精益码农";
    
    var f =  s.TryGetValue("WeChatID",out  object obj);
    
    Console.WriteLine(f);
    Console.WriteLine(obj);
    

    会输出如下结果:

    是不是很意外。


    但是看官们一般不会使用MemoryCache的原生方法,而是使用位于同一命名空间的
    扩展方法Set

    var s = new MemoryCache(new MemoryCacheOptions { });
    s.Set("WeChatID", "精益码农");
    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关键字 (也说明了CacheEntry继承自IDisposable接口)。

    继续追溯CacheEntry实现的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<object, CacheEntry>集合插入缓存项,

    综上:缓存项CacheEntry需要被Dispose,才能被插入MemoeyCache

    这是怎样的设计模式?
    IDisposable接口不是用来释放资源吗?
    为啥要使用Dispose方法来向MemoryCache插值?
    不能使用一个明确的Commit方法吗?

    这在Github上也有issue讨论,从2017年开始就有大佬质疑这是一个反人类的设计思路,官方为了不引入Break Change,一直保持到现在。


    基于此现状,我们如果使用MemoryCache的原生插值方法, 需要这样:

     var s = new MemoryCache(new MemoryCacheOptions { });
     using (var entry = s.CreateEntry("WeChatID"))
     {
          entry.Value = "精益码农";
     }
     var f = s.TryGetValue("WeChatID", out object obj);
     ...
    

    尽量不要使用C#8.0推出的不带大括号的using语法

     using var entry = s.CreateEntry("WeChatID");
     entry.Value = "精益码农";
                
     var f = s.TryGetValue("WeChatID", out object obj);
     ...
    

    这种没明确指定using作用范围的语法,会在函数末尾才执行Dispose方法, 导致执行到TryGetValue时,缓存项其实还没插入!!!

    Last
    1. MemoryCache插值的实现过程很奇葩
    2. 尽量使用带明确大括号范围的using语法,C#8.0推出的不带大括号的using语法糖的作用时刻在函数末尾,会带来误导。

    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15748417.html

    欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化
    上海鲜花港 - 郁金香
  • 相关阅读:
    Java Springboot webSocket简单实现,调接口推送消息到客户端socket
    对象实体和对象引用的区别
    SpringBoot中JPA使用动态SQL查询
    windows10环境安装RabbitMQ
    SpringBoot集成ElasticSearch
    SpringBoot+神通数据库+JPA
    【bug记录】jpa 解决org.hibernate.lazyinitializationexception could not initialize proxy
    mysql 语句中 sum函数求和 null 变 0
    springBoot文件下载跨域问题+前端访问后台下载方法不弹出下载框的问题
    C# HTTP Get Post 提交数据可以指定代理IP、指定浏览器、指定来源
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15748417.html
Copyright © 2020-2023  润新知