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;

如果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;
}

三、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;
}

原文地址:https://www.cnblogs.com/qixinbo/p/11534379.html

时间: 2024-10-03 22:29:04

Core源码(三) Lazy<T>的相关文章

spark core源码分析6 Spark job的提交

本节主要讲解SparkContext的逻辑 首先看一个spark自带的最简单的例子: object SparkPi { def main(args: Array[String]) { val conf = new SparkConf().setAppName("Spark Pi") val spark = new SparkContext(conf) val slices = if (args.length > 0) args(0).toInt else 2 val n = ma

yii源码三 -- db

CDbConnection:path:/framework/db/CDbConnection.phpoverview:CDbConnection represents a connection to a database. 工作原理:CDbConnection works together with CDbCommand, CDbDataReader and CDbTransaction to provide data access to various DBMS.且基于PDO扩展. 首先用$c

asp.net core源码飘香:Logging组件(转)

简介: 作为基础组件,日志组件被其他组件和中间件所使用,它提供了一个统一的编程模型,即不需要知道日志最终记录到哪里去,只需要调用它即可. 使用方法很简单,通过依赖注入ILogFactory(CreateLogger方法)或ILogger<T>对象,获取一个ILogger对象,然后通过ILogger的各种扩展方法(都是调用Log方法)记录不同级别的日志. 源码剖析: 总结: 日志组件其实就是工厂模式的应用,但进行了改进,LoggerFactory每次都返回一个Logger对象,而Logger对象

asp.net core源码飘香:Configuration组件(转)

简介: 这是一个基础组件,是一个统一的配置模型,配置可以来源于配置文件(json文件,xml文件,ini文件),内存对象,命令行参数,系统的环境变量又或者是你自己扩展的配置源,该组件将各个配置源的数据按统一的格式(IDictionary<string, string> Data)进行加载,进而对外提供调用接口. 不仅如此,有些配置源(如文件配置源)还可以在配置源的数据发生变化时进行重新加载(IDictionary<string, string> Data),而程序员随时可以判断是否

asp.net core源码地址

https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet 这个下面是asp.net core 框架的地址,里面有很多仓库. https://github.com/aspnet/EntityFrameworkCore  EF Core源码 https://github.com/aspnet/Configuration 配置模块源码 https://github.com/aspnet/Routing

一起来看CORE源码(一) ConcurrentDictionary

先贴源码地址 https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs .NET CORE很大一个好处就是代码的开源,你可以详细的查看你使用类的源代码,并学习微软的写法和实现思路. 这里我对.net core中ConcurrentDictionary源码进行了分析,里面采用了Vola

DOTNET CORE源码分析之IOC容器结果获取内容补充

补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngine.ServiceProviderEngine和ServiceProviderEngineScope 中还没有关联上ServiceProvider和ServiceCollection就直接通过GetService获取了值,这样不科学啊.其实是有关联的,请看一下上篇文章同样存在的一个代码段: inte

CAD ObjectARX扩展工具的源码(三)

CAD ObjectARX扩展工具的源码(三)//得到文本边界oid CDrawFunction::getTextBoundary(AcDbObjectId objectId,double offset,AcDbObjectId &textBoundaryId){AcDbExtents Ext;AcDbEntity *pEnt;acdbOpenObject(pEnt,objectId,AcDb::kForWrite);if(pEnt->isKindOf(AcDbText::desc())){

asp.net core源码飘香:从Hosting开始

知识点: 1.Kestrel服务器启动并处理Http请求的过程. 2.Startup的作用. 源码飘香: 总结: asp.net core将web开发拆分为多个独立的组件,大多以http中间件的形式添加到请求管线,也有一下作为基础组件(configution,options,logging等)提供. 零零散散的二三十个框架吧,除了mvc(还有razor)和ef core是比较大型的框架,其他小框架都可以一遍博客讲完一个.后面三个大框架准备开单独的系列来细说. 今晚就到此为止吧,如果大家有兴趣,请