【C# 线程】 延迟初始化


1. 简介

1、延迟初始化出现于.NET 4.0,主要用于提高性能,避免浪费计算,并减少程序内存要求。也可以称为,按需加载。

2、从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率,同时Lazy是天生线程安全的

3、注:Lazy 对象初始化默认是线程安全的,在多线程环境下,第一个访问 Lazy 对象的 Value 属性的线程将初始化 Lazy 对象,以后访问的线程都将使用第一次初始化的数据。

2. 应用场景

  • 1、大对象和耗时对象

    对象创建成本高且程序可能不会使用它。 例如,假定内存中有具有 Orders 属性的 Customer 对象,该对象包含大量 Order 对象,初始化这些对象需要数据库连接。 如果用户永远不要求显示 Orders 或在计算中使用该数据,则无需使用系统内存或计算周期来创建它。 通过使用 Lazy 来声明 Orders 对象用于迟缓初始化,可以避免在不使用该对象时浪费系统资源。对象创建成本高,且希望将其创建推迟到其他高成本操作完成后。 例如,假定程序在启动时加载多个对象实例,但是只需立即加载其中一部分。 可以通过推迟初始化不需要的对象,直到创建所需对象,提升程序的启动性能。

  • 2、封装方法

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

虽然可以编写自己的代码来执行迟缓初始化,但我们建议使用 Lazy。 Lazy 及其相关的类型还支持线程安全并提供一致的异常传播策略。

下表列出了 .NET Framework 版本 4 提供的在不同方案中启用迟缓初始化的类型。

表 1
类型说明
Lazy 为任何类库或用户定义类型提供迟缓初始化语义的包装类。
ThreadLocal 类似于 Lazy,除了该包装类基于线程本地提供迟缓初始化语义。 每个线程都可以访问自己唯一的值。
LazyInitializer 为对象的迟缓初始化提供高级 static(Visual Basic 中的 Shared)方法,无需支付类的成本。

Lazy构造函数

构造函数
Lazy()

初始化 Lazy 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数。

Lazy(Boolean)

初始化 Lazy 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数和指定的初始化模式。

Lazy(Func)

初始化 Lazy 类的新实例。 出现迟缓初始化时,将使用指定的初始化函数。

Lazy(Func, Boolean)

初始化 Lazy 类的新实例。 当延迟初始化发生时,将使用指定的初始化函数和初始化模式。

传入方法,用来return value。

Lazy(Func, LazyThreadSafetyMode)

初始化 Lazy 类的新实例,它使用指定的初始化函数和线程安全模式。

传入方法,用来return value。

Lazy(LazyThreadSafetyMode)

初始化 Lazy 类的新实例,其中使用 T 的无参数构造函数和指定的线程安全性模式。

Lazy(T)

初始化 Lazy 类的新实例,该类使用已预先初始化的指定值。

 

 

Lazy.Value属性的使用

默认值或者方法的返回值

Lazy对象创建后,并不会立即创建对应的对象,一旦使用.Vale,那么对应的变量就会被实例化,IsValueCreated属性也就变成了true。

Value属性是只读的,也就意味着如果Value存储了引用类型,将无法为其分配新对象,只可以更改此对象公共的属性或者字段等,如果Value存储的是值类型,那么就不能修改其值了,只能通过再次调用变量的函数使用新的参数来创建新的变量

在Lazy对象创建后,在首次访问变量的Value属性前,

Lazy.IsValueCreated属性的使用

判断是实例化

LazyThreadSafetyMode 枚举

指定 Lazy 实例如何同步多个线程间的访问。

 

所有此类构造函数都是完全线程安全的。

在内部使用锁,则可能会发生死锁

ExecutionAndPublication  
不是线程安全的。 None false 不适用。
完全线程安全;用于初始化值的线程争用。 PublicationOnly

 

 

 

构造时使用默认的初始化方式

using System;

namespace LazyUsage
{
    class LazyDemo
    {
        static void Main()
        {
            Lazy lazyData = new Lazy();
            Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
            lazyData.Value.Print();//此处访问时才会将Data真正的初始化
            Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);

            Console.ReadKey();
        }
    }

    class Data
    {
        public Data() 
        {
            Console.WriteLine("Data::.ctor->Initialized");
        }

        public void Print()
        {
            Console.WriteLine("Data::Print->println");
        }
    }
}

输出结果

Main->is lazyData Initialized? value = False
Data::.ctor->Initialized
Data::Print->println
Main->is lazyData Initialized? value = True

构造时使用指定的委托初始化

using System;

namespace LazyUsage
{
    class LazyDemo
    {
        static void Main()
        {
            //指定委托来初始化Data
            Lazy lazyData = new Lazy(
                () =>
                {
                    Console.WriteLine("Main->lazyData will be Initialized!");
                    return new Data("Test");
                });
            Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
            lazyData.Value.Print();
            Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);


            Console.ReadKey();
        }
    }

    class Data
    {
        public string Name { get; private set; }

        public Data(string name)
        {
            Name = name;
            Console.WriteLine("Data::.ctor->Initialized,name = "+name);
        }

        public void Print()
        {
            Console.WriteLine("Data::Print->name = " + Name);
        }
    }
}

输出结果

Main->is lazyData Initialized? value = False
Main->lazyData will be Initialized!
Data::.ctor->Initialized,name = Test
Data::Print->name = Test
Main->is lazyData Initialized? value = True

 

扩展. 实现延迟属性

class Customer
{
    private Lazy _orders;

    public string CustomerID {get; private set;}

    public Customer(string id)
    {
        CustomerID = id;
        _orders = new Lazy(() =>
        {
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            return _orders.Value;
        }
    }
}

 ThreadLocal

该类相当与一个线程结界,将变量的值和作用限制在线程中。所以用该类包装过的类型是线程安全的。该类包装过的类型变量的只能在该线程中使用,其他线程包括子线程无法使用。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

构造函数

构造函数
ThreadLocal()

初始化 ThreadLocal 实例。

ThreadLocal(Boolean)

初始化 ThreadLocal 实例并指定是否可从任意线程访问所有值。

ThreadLocal(Func)

使用指定的 valueFactory 函数初始化 ThreadLocal 实例。传入方法 用来return value

ThreadLocal(Func, Boolean)

使用指定的 valueFactory 函数和指示是否可从任意线程访问所有值的标志初始化 ThreadLocal 实例。

传入方法 用来return value

 

 

 

 

 

属性

属性
IsValueCreated

获取是否在当前线程上初始化 Value。用法类似lazy

Value

数据类型的默认值或者方法返回值。用法类似lazy

Values

获取当前由已经访问此实例的所有线程存储的所有值的列表。

提供线程数据的本地存储。

static void Main(string[] args)
{
     ThreadLocal<int> threadLocal = new ThreadLocal<int>();
     //在主线程这个变量值为1
     threadLocal.Value = 1;
     new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
     Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value}");
}

 输出结果:
托管线程ID:10 值为:0
主线程ID:1 值为:1
托管线程ID:11 值为:0
托管线程ID:12 值为:0

using System;
using System.Threading;
using System.Threading.Tasks;

class ThreadLocalDemo
{
    
        // Demonstrates:
        //      ThreadLocal(T) constructor
        //      ThreadLocal(T).Value
        //      One usage of ThreadLocal(T)
        static void Main()
        {
            // Thread-Local variable that yields a name for a thread
            ThreadLocal<string> ThreadName = new ThreadLocal<string>(() =>
            {
                return "Thread" + Thread.CurrentThread.ManagedThreadId;
            });

            // Action that prints out ThreadName for the current thread
            Action action = () =>
            {
                // If ThreadName.IsValueCreated is true, it means that we are not the
                // first action to run on this thread.
                bool repeat = ThreadName.IsValueCreated;

                Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : "");
            };

            // Launch eight of them.  On 4 cores or less, you should see some repeat ThreadNames
            Parallel.Invoke(action, action, action, action, action, action, action, action);

            // Dispose when you are done
            ThreadName.Dispose();
        }
}
// This multithreading example can produce different outputs for each 'action' invocation and will vary with each run.
// Therefore, the example output will resemble but may not exactly match the following output (from a 4 core processor):
// ThreadName = Thread5 
// ThreadName = Thread6 
// ThreadName = Thread4 
// ThreadName = Thread6 (repeat)
// ThreadName = Thread1 
// ThreadName = Thread4 (repeat)
// ThreadName = Thread7 
// ThreadName = Thread5 (repeat)
C