• Core源码(三) Lazy<T>


    Lazy<T>解决什么问题?

    1、大对象加载

      考虑下面的需求,有个对象很大,创建耗时,并且要在托管堆上分配一大块空间。我们当然希望,用到它的时候再去创建。也就是延迟加载,等到真正需要它的时候,才去加载。

      显然,这里需要加一个中间层,将大对象封装起来,暴露接口,开始并不创建大对象,等到用户真正访问对象的时候,再去创建。另外,这个中间层应该可以封装不同类型的大对象,因此需要类模版。Lazy<T>就是为了解决这个问题。

      典型的使用

    public Lazy<AccountService> AccountServ = new Lazy<AccountService>();
    public Lazy<ProductService> ProductService = new Lazy<ProductService>();

    2、将委托或者方法对象保存,并在需要的时候调用。

    private readonly Lazy<IDbConnection> _connectionLazy;
    public CallHistoryRepository(ConnectionFactory connectionFactory)
    {
        _connectionLazy = new Lazy<IDbConnection>(()=>connectionFactory.Connection);
    }

    一旦使用.Vale,那么对应的变量就会被实例化,IsValueCreated属性也就变成了true。

    实现自己的Lazy<T>

      在.NET Framework 4.0之前,大对象就是存在的,那么对于一个大型系统而言,怎么样对付一个大对象呢。主要有两点:延迟加载即时清理。前者解决创建问题,后者解决回收问题。

      那么在来看Lazy<T>的.NET Framework实现之前,我们先来自己实现一个简单的Lazy<T>吧。

    class MyLazy<T> where T : new()
    {
        private T value;
        private bool isLoaded;
        public MyLazy()
        {
            isLoaded = false;
        }
        public T Value
        {
            get
            {
                if (!isLoaded)
                {
                    value = new T();
                    isLoaded = true;
                }
                return value;
            }
        }
    }

    这应该是最简单版本的Lazy<T>了,没有线程安全检测,只有着访问时创建真实对象,可是对于我们一般的应用来说也许就已经足够了。

    .NET Lazy<T> 实现

    .NET Core和我们的实现,有两点主要的不同:

    1、 引入了Boxed内部类:

    [Serializable]
    private class Boxed
    {
        // Fields
        internal T m_value;
    
        // Methods
        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        internal Boxed(T value)
        {
            this.m_value = value;
        }
    }

      该内部类取代了我在上面实现中的泛型约束,使之更通用。

      但是我们也应该注意到,如果T为结构体,那么由于T很大,所以装箱拆箱反而也许是个更耗费效率的事情,因此,个人建议,对值类型慎用Lazy<T>

    2、 线程安全的控制

    在线程安全的控制选项中,.NET Framework为我们提供了这样的枚举选项:

    public enum LazyThreadSafetyMode
    {
        None,
        PublicationOnly,
        ExecutionAndPublication
    }

    默认值为ExecutionAndPublication

    枚举选项MSDN介绍如下

    http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode%28VS.100%29.aspx

    isThreadSafe则应用于多线程环境下,如果isThreadSafe为false,那么延迟加载对象则一次只能创建于一个线程。

    Lazy<T>源码

    System.Runtime命名空间下

    由于core中的lazy源码我只找到了下图这个,再往下lazy的实现并没找

    所以我使用.Net 4.5的lazy源码

     

    一、最常使用的属性Value

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public T Value
    {
        get
        {
            Boxed boxed = null;
            if (m_boxed != null )
            {
                // Do a quick check up front for the fast path.
                boxed = m_boxed as Boxed;
                if (boxed != null)
                {
                    return boxed.m_value;
                }
                LazyInternalExceptionHolder exc = m_boxed as LazyInternalExceptionHolder;
                Contract.Assert(m_boxed != null);
                exc.m_edi.Throw();
            }
            return LazyInitValue();
        }
    }
    //null --> value is not created
    //m_value is Boxed --> the value is created, and m_value holds the value
    //m_value is LazyExceptionHolder --> it holds an exception
    private object m_boxed;
    View Code

    如果m_boxed有值,就直接装箱返回对应值(这里就要注意值类型装箱的性能损失了)。这个装箱是为了检验下m_boxed的值,因为其中有可能是异常。

    二、LazyInitValue方法

    如果m_boxed为空,就调用LazyInitValue方法,这里有针对线程安全模式的判断

    /// <summary>
    /// local helper method to initialize the value 
    /// </summary>
    /// <returns>The inititialized T value</returns>
    private T LazyInitValue()
    {
        Boxed boxed = null;
        LazyThreadSafetyMode mode = Mode;
        if (mode == LazyThreadSafetyMode.None)
        {
            boxed = CreateValue();
            m_boxed = boxed;
        }
        else if (mode == LazyThreadSafetyMode.PublicationOnly)
        {
            boxed = CreateValue();
            if (boxed == null ||
                Interlocked.CompareExchange(ref m_boxed, boxed, null) != null)
            {
                // If CreateValue returns null, it means another thread successfully invoked the value factory
                // and stored the result, so we should just take what was stored.  If CreateValue returns non-null
                // but we lose the ---- to store the single value, again we should just take what was stored.
                boxed = (Boxed)m_boxed;
            }
            else
            {
                // We successfully created and stored the value.  At this point, the value factory delegate is
                // no longer needed, and we don't want to hold onto its resources.
                m_valueFactory = ALREADY_INVOKED_SENTINEL;
            }
        }
        else
        {
            object threadSafeObj = Volatile.Read(ref m_threadSafeObj);
            bool lockTaken = false;
            try
            {
                if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL)
                    Monitor.Enter(threadSafeObj, ref lockTaken);
                else
                    Contract.Assert(m_boxed != null);
    
                if (m_boxed == null)
                {
                    boxed = CreateValue();
                    m_boxed = boxed;
                    Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL);
                }
                else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
                {
                    boxed = m_boxed as Boxed;
                    if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
                    {
                        LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
                        Contract.Assert(exHolder != null);
                        exHolder.m_edi.Throw();
                    }
                }
            }
            finally
            {
                if (lockTaken)
                    Monitor.Exit(threadSafeObj);
            }
        }
        Contract.Assert(boxed != null);
        return boxed.m_value;
    }
    View Code

    三、CreateValue方法

    返回一个实例对象T,采用传入的func方法,这里会对是否已经返回做出判断,如果已经返回,就返回null

    /// <summary>Creates an instance of T using m_valueFactory in case its not null or use reflection to create a new T()</summary>
    /// <returns>An instance of Boxed.</returns>
    private Boxed CreateValue()
    {
        Boxed boxed = null;
        LazyThreadSafetyMode mode = Mode;
        if (m_valueFactory != null)
        {
            try
            {
                // check for recursion
                if (mode != LazyThreadSafetyMode.PublicationOnly && m_valueFactory == ALREADY_INVOKED_SENTINEL)
                    throw new InvalidOperationException(Environment.GetResourceString("Lazy_Value_RecursiveCallsToValue"));
                Func<T> factory = m_valueFactory;
                if (mode != LazyThreadSafetyMode.PublicationOnly) // only detect recursion on None and ExecutionAndPublication modes
                {
                    m_valueFactory = ALREADY_INVOKED_SENTINEL;
                }
                else if (factory == ALREADY_INVOKED_SENTINEL)
                {
                    // Another thread ----d with us and beat us to successfully invoke the factory.
                    return null;
                }
                boxed = new Boxed(factory());
            }
            catch (Exception ex)
            {
                if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
                    m_boxed = new LazyInternalExceptionHolder(ex);
                throw;
            }
        }
        else
        {
            try
            {
                boxed = new Boxed((T)Activator.CreateInstance(typeof(T)));
    
            }
            catch (System.MissingMethodException)
            {
                Exception ex = new System.MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT"));
                if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
                    m_boxed = new LazyInternalExceptionHolder(ex);
                throw ex;
            }
        }
        return boxed;
    }
    View Code
  • 相关阅读:
    LeetCode15.3 Sum
    LeetCode215. Kth Largest Element in an Array
    python基础结构的时间复杂度
    顺时针打印矩阵
    合并k个有序链表
    LeetCode3. Longest Substring Without Repeating Characters
    决策树剪枝问题
    LeetCode98. Validate Binary Search Tree
    LeetCode96. Unique Binary Search Trees
    Visio软件不能使用方向键移动图形的解决办法
  • 原文地址:https://www.cnblogs.com/qixinbo/p/11534379.html
Copyright © 2020-2023  润新知