C#Lazy 实现延迟加载详解与示例

在C#中,Lazy< T> 类是一个非常有用的工具,它可以用于延迟加载值,尤其是在创建对象时可能很昂贵,或者你想要延迟初始化直到真正需要该值的情况下。在本文中,我们将详细介绍 Lazy< T> 的实现机制和用法,并提供一些示例来展示它的优势。

1、Lazy 的工作原理

Lazy< T> 类是.NET框架中的一个并发类,它允许你延迟初始化一个对象,直到这个对象被第一次使用时才进行。这意味着,如果多个线程需要访问同一个延迟初始化的对象,Lazy< T> 能够保证只有一个线程会执行初始化代码,从而避免不必要的资源消耗。

Lazy< T> 采用懒汉式初始化模式,在.NET Framework 4.0及之前的版本中,它是线程安全的,采用内部互斥锁(Mutex)来确保线程安全。但在.NET 4.0之后,Lazy< T> 采用了新的LazyInitializationMode.None模式,允许非线程安全且更高效的初始化,这时需要开发者自己确保初始化的线程安全。

2、创建 Lazy 实例

要创建一个 Lazy< T> 实例,你可以使用以下构造函数:

Lazy<T>() : this(LazyThreadSafetyMode.ExecutionAndPublication)
Lazy<T>(Func<T> valueFactory) : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication)
Lazy<T>(LazyThreadSafetyMode mode)
Lazy<T>(Func<T> valueFactory, LazyThreadSafetyMode mode)

LazyThreadSafetyMode 是一个枚举,用于指定初始化时的线程安全模式。有四种模式:

  • LazyThreadSafetyMode.None:允许非线程安全初始化。
  • LazyThreadSafetyMode.ExecutionAndPublication:执行初始化时是线程安全的,且Publish方法也是线程安全的。
  • LazyThreadSafetyMode.PublicationOnly:仅Publish方法是线程安全的。
  • LazyThreadSafetyMode.UnprotectedPublication:既不是执行时也不是发布时线程安全。

3、 使用 Lazy

一旦你创建了一个 Lazy< T> 实例,你可以通过其 Value 属性来获取其内部值的引用,该属性是只读的,并会在第一次访问时触发值的初始化。

4、示例

下面我们通过一个示例来演示如何使用 Lazy 进行延迟加载。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;class Program
{static void Main(string[] args){// 使用 Lazy<T> 创建一个延迟加载的对象Lazy<ExpensiveObject> lazyExpensiveObject = new Lazy<ExpensiveObject>(() => new ExpensiveObject(), LazyThreadSafetyMode.ExecutionAndPublication);// 获取对象值,这将触发延迟加载ExpensiveObject expensiveObject = lazyExpensiveObject.Value;// 使用 expensiveObject 做些事情Console.WriteLine(expensiveObject.SomeProperty);Console.ReadKey();}
}class ExpensiveObject
{public ExpensiveObject(){// 模拟一个初始化代价很昂贵的操作Console.WriteLine("Expensive object initialized.");}public string SomeProperty { get; set; }
}

在这个示例中,我们创建了一个 ExpensiveObject 的 Lazy 实例。这个 ExpensiveObject 的构造函数是一个耗时的操作。当我们第一次访问 lazyExpensiveObject.Value 时,构造函数会被调用,并且 ExpensiveObject 实例会被创建。注意,之后对这个属性的所有访问都会直接返回已经创建的实例,而不会再次调用构造函数。

注意事项

  1. 如果你需要在多个线程中共享延迟加载的对象,请确保你正确同步对这个对象的访问。
  2. 如果你的初始化操作是线程安全的,你可以使用 LazyThreadSafetyMode.ExecutionAndPublication,这样可以保证初始化过程和发布过程都是线程安全的。
  3. 如果你的初始化操作不依赖于外部状态,并且你确信它可以在多个线程中安全地并行执行,你可以使用 LazyThreadSafetyMode.None,这将避免线程锁定,并可能提高性能。

5、Lazy< T> 实现延迟加载

Lazy< T> 利用了 C# 的属性器和反射机制来实现延迟加载。当访问 Lazy< T> 的 Value 属性时,如果内部值尚未初始化,则初始化它。这个过程称为“lazy initialization”。Lazy< T> 提供了几种不同的线程安全模式,以适应不同的场景。

实现方式

下面是使用 Lazy 进行延迟加载资源的基本步骤:

  1. 创建一个 Lazy 实例,并通过提供一个函数来指定要延迟加载的资源。
  2. 在需要的时候,通过访问 Lazy 的 Value 属性来触发资源的加载。

示例:延迟加载图片

假设我们有以下一个类,它使用 Lazy 来延迟加载图片:

using System;
using System.Drawing;
using System.Threading.Tasks;public class ImageLoader
{private Lazy<Bitmap> _lazyImage = new Lazy<Bitmap>(() => LoadImageAsync("path/to/image.jpg"), LazyThreadSafetyMode.ExecutionAndPublication);public Bitmap GetImage(){return _lazyImage.Value;}private async Task<Bitmap> LoadImageAsync(string imagePath){using (var stream = new FileStream(imagePath, FileMode.Open)){return (Bitmap)Image.FromStream(stream);}}
}

在这个例子中,ImageLoader 类有一个 Lazy 实例,它通过异步方法 LoadImageAsync 加载图片。当调用 GetImage 方法时,Lazy 会触发 LoadImageAsync 的执行,并返回图片。

示例:延迟加载视频

视频加载通常涉及到更复杂的操作,下面是一个简化的例子:

using System;
using System.IO;
using System.Threading.Tasks;public class VideoLoader
{private Lazy<FileStream> _lazyVideoStream = new Lazy<FileStream>(() => LoadVideoAsync("path/to/video.mp4"), LazyThreadSafetyMode.ExecutionAndPublication);public FileStream GetVideoStream(){return _lazyVideoStream.Value;}private async Task<FileStream> LoadVideoAsync(string videoPath){return new FileStream(videoPath, FileMode.Open);}
}

在这个例子中,VideoLoader 类使用 Lazy 来延迟加载视频文件流。当 GetVideoStream 被调用时,视频文件流会被创建并返回。

示例:延迟加载音频

音频文件的加载可以类似于视频文件的加载:

using System;
using System.IO;
using System.Threading.Tasks;public class AudioLoader
{private Lazy<FileStream> _lazyAudioStream = new Lazy<FileStream>(() => LoadAudioAsync("path/to/audio.wav"), LazyThreadSafetyMode.ExecutionAndPublication);public FileStream GetAudioStream(){return _lazyAudioStream.Value;}private async Task<FileStream> LoadAudioAsync(string audioPath){return new FileStream(audioPath, FileMode.Open);}
}

在这个例子中,AudioLoader 类使用 Lazy 来延迟加载音频文件流。当 GetAudioStream 被调用时,音频文件流会被创建并返回。

6、如何在多线程环境中测试 Lazy<T> 的线程安全性?

在多线程环境中测试 Lazy< T> 的线程安全性通常涉及到模拟 concurrent access(并发访问)来确保 Lazy< T> 在不同线程之间正确地处理初始化和访问。这里有几种方法可以用来测试 Lazy< T> 的线程安全性:

  1. 使用 Lazy< T> 的同步模式: 在 Lazy 的构造函数中指定 LazyThreadSafetyMode.ExecutionAndPublication 或 LazyThreadSafetyMode.PublicationOnly,这样 Lazy< T> 会确保在多个线程中的执行和发布都是线程安全的。
  2. 手动同步: 如果你使用的是 LazyThreadSafetyMode.None,你需要手动同步对 Lazy< T> 属性的访问。这可以通过 lock 语句或 Monitor 类来实现。
  3. 使用 Task 和 Parallel 类: 使用 Task 并行库来创建多个任务,每个任务访问 Lazy 的 Value 属性。确保在所有任务都完成时,Lazy< T> 的初始化只执行一次。
  4. 使用 Mutex 或 Semaphore: 使用 Mutex 或 Semaphore 来控制对 Lazy< T> 初始化代码的访问,确保初始化是独占进行的。
  5. 单元测试: 编写单元测试来模拟并发访问。可以使用测试框架(如 NUnit 或 xUnit)来创建多个测试线程,并确保它们正确地访问 Lazy< T>。
  6. 代码分析工具: 使用像 NDepend 或 SonarQube 这样的代码分析工具来检测可能的线程安全问题。
    下面是一个简单的示例,展示了如何在单元测试中使用 Lazy< T> 和 Task 来测试线程安全性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;public class LazyTestClass
{private Lazy<List<int>> _lazyList = new Lazy<List<int>>(() => new List<int>(), LazyThreadSafetyMode.ExecutionAndPublication);public List<int> GetList(){return _lazyList.Value;}
}public class Program
{public static void Main(){LazyTestClass testClass = new LazyTestClass();// 创建多个任务来并发访问 Lazy<T>var tasks = Enumerable.Range(1, 10).Select(i => Task.Run(() => testClass.GetList()));// 等待所有任务完成Task.WaitAll(tasks.ToArray());}
}// 单元测试
public class LazyTestClassTests
{[Fact]public void TestLazyThreadSafety(){LazyTestClass testClass = new LazyTestClass();// 创建多个测试线程var tasks = Enumerable.Range(1, 10).Select(i => Task.Run(() => testClass.GetList()));// 等待所有任务完成Task.WaitAll(tasks.ToArray());// 断言列表的实例只有一个Assert.Single(tasks.Select(t => t.Result));}
}

在这个示例中,我们创建了一个 LazyTestClass,它有一个 Lazy<List> 成员。我们在主函数中创建了多个 Task 来并发地访问 GetList 方法,该方法返回 Lazy<List> 的值。在单元测试中,我们使用 Fact 属性来标记一个测试方法,并使用 Assert.Single 来断言只有一个 List 实例被创建。

7、Lazy 加载在性能和用户体验方面的作用

Lazy 加载技术可以显著提高程序的性能和用户体验。以下是它在不同方面的一些潜在作用:

  • 性能提升:通过延迟加载昂贵的资源,程序可以在不需要这些资源时避免不必要的开销。这意味着资源只有在真正需要时才会被加载,从而减少内存和CPU的使用。
  • 响应性增强:在用户界面(UI)中使用 Lazy 加载可以避免在初始加载时延迟UI的响应。这对于创建快速启动的应用程序至关重要。
  • 资源优化:对于大型资源,如图片、视频和音频文件,Lazy 加载确保只有在用户请求时才加载它们,这样可以减少应用程序的整体大小和加载时间。
  • 多线程支持:Lazy 加载在多线程环境中自动同步,这意味着不必担心在多个线程中共享和初始化资源的问题。

8、安全性和效率考虑

尽管 Lazy 加载提供了许多好处,但在使用时也需要考虑安全和效率:

  • 线程安全:Lazy 加载默认是线程安全的,但在自定义 Lazy 实现或使用 LazyThreadSafetyMode.None 时,需要确保线程安全。
  • 资源泄漏:如果异步加载的资源没有正确管理(例如,没有释放或关闭流),可能会导致资源泄漏。
  • 性能开销:即使是 Lazy 加载,如果初始化过程很昂贵,或者在短时间内多次调用 Value 属性,这可能会导致性能问题。
  • 过度依赖:过度使用 Lazy 加载可能会导致代码难以理解和维护,特别是当依赖关系变得复杂时。

总结

Lazy< T> 是C#中一个非常有用的并发特性,它允许开发者延迟初始化对象,直到这些对象真正被需要。通过正确使用 Lazy< T>,你可以优化应用程序的性能,减少资源消耗,并提高应用程序的响应性。

在使用 Lazy< T> 时,你需要仔细考虑线程安全问题,并选择合适的 LazyThreadSafetyMode。此外,你还需要确保在多个线程中共享延迟加载的对象时,你的初始化代码和发布代码都是线程安全的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/829133.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营day40

题目&#xff1a;343. 整数拆分、96.不同的二叉搜索树 参考链接&#xff1a;代码随想录 343. 整数拆分 思路&#xff1a;五部曲来走。dp数组&#xff0c;dp[i]用于记录拆i得到的最大乘积和&#xff0c;我们要求的也就是dp[n]&#xff1b;递推公式&#xff0c;我们想拆分i&am…

ZooKeeper集群的搭建

ZooKeeper集群的搭建 将master节点的/data目录下的ZooKeeper安装包解压到/opt/software目录下 tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz -C /opt/software/在master节点切换至ZooKeeper安装目录的conf目录下&#xff0c;将zoo_sample.cfg重命名为zoo.cfg&#xff0c;并…

OpenHarmony语言基础类库【@ohos.util.LinkedList (线性容器LinkedList)】

LinkedList底层通过双向链表实现&#xff0c;双向链表的每个节点都包含对前一个元素和后一个元素的引用。当需要查询元素时&#xff0c;可以从头遍历&#xff0c;也可以从尾部遍历&#xff0c;插入、删除效率高&#xff0c;查询效率低。LinkedList允许元素为null。 LinkedList…

基于Springboot的点餐平台

基于SpringbootVue的点餐平台的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 菜品信息 菜品资讯 购物车 后台登录 用户管理 菜品分类管理 菜品信息管理 …

#ESP32S3N8R8(按键点灯)

一、按键对应端口为GPIO0&#xff08;上拉&#xff09; 二、代码 #include <stdio.h> #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unistd.h"void app_main(void) {int co…

YOLOv8 实现车牌检测,生成可视化检测视频(20240424)

原项目源码地址&#xff1a;GitHub 我的源码地址&#xff1a;Gitee 环境搭建请参考&#xff1a;Win10 搭建 YOLOv8 运行环境&#xff08;20240423&#xff09;-CSDN博客 环境测试请参考&#xff1a;本地运行测试 YOLOv8&#xff08;20240423&#xff09;-CSDN博客 训练数据…

【java数据结构-优先级队列向下调整Topk问题,堆的常用的接口详解】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 …

OpenHarmony实战开发-媒体查询 (@ohos.mediaquery)

概述 媒体查询作为响应式设计的核心&#xff0c;在移动设备上应用十分广泛。媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。媒体查询常用于下面两种场景&#xff1a; 针对设备和应用的属性信息&#xff08;比如显示区域、深浅色、分辨率&#xff09;&#xff0…

异步日志方案spdlog

异步日志方案spdlog spdlog 是一款高效的 C 日志库&#xff0c;它以其极高的性能和零成本的抽象而著称。spdlog 支持异步和同步日志记录&#xff0c;提供多种日志级别&#xff0c;并允许用户将日志输出到控制台、文件或自定义的接收器。 多线程使用和同步、异步日志没有关系是…

Linux系统----信号(万字文章超级详细并且简单易学附有实操shell指令图及注释!)

绪论​ “Do one thing at a time, and do well.”&#xff0c;本章开始Linux系统其中信号是学习操作系统的基本下面将会讲到什么是信号、信号的多种产生方式、信号如何保存的、信号如何处理的、以及一些信号的细节。话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑…

【鸿蒙】通知

一、概要 Android的Notification。 说到通知&#xff0c;就想到了推送。 通知这块可以做到不像Android一样需要集成各家厂商的推送了&#xff0c;不知道是否有建立独立的推送系统 这是官网上介绍的跨APP进行的IPC通知。实际在Android开发过程中&#xff0c;可能这种场景会相对…

MarginNote 3 for Mac:一站式思维导图与笔记神器,让学习更高效

MarginNote 3 for Mac是一款功能强大的阅读和学习工具软件&#xff0c;它将PDF/EPUB阅读器和多种学习工具集成起来&#xff0c;旨在帮助用户更有效地进行阅读、笔记整理以及知识管理。 这款软件的核心功能在于其能够将阅读与学习过程紧密结合。用户可以在阅读文档时&#xff0…

勒索软件安全防护手册

文章目录 相关背景勒索软件概述勒索软件主要类型文件加密类勒索软件数据窃取类勒索软件系统加密类勒索软件。屏幕锁定类勒索软件 勒索软件典型传播方式利用安全漏洞传播利用钓鱼邮件传播利用网站挂马传播利用移动介质传播利用软件供应链传播利用远程桌面入侵传播 典型勒索软件攻…

自动驾驶传感器篇: GNSSIMU组合导航

自动驾驶传感器篇&#xff1a; GNSS&IMU组合导航 1.GNSS1.1 GNSS 系统概述1.2 GNSS系统基本组成1. 空间部分&#xff08;Space Segment&#xff09;&#xff1a;2. 地面控制部分&#xff08;Ground Control Segment&#xff09;&#xff1a;3. 用户设备部分&#xff08;Use…

Stable Diffusion WebUI 使用 VAE 增加滤镜效果

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要介绍 VAE 模型&#xff0c;主要内容有&#xff1a;VAE 模型的概念、如果下载 VAE 模型、如何安装 VAE 模型、如…

开箱展示——深圳市雷龙发展的存储卡

最近收到了来自深圳市雷龙发展有限公司寄来的存储卡&#xff0c;奈何最近也没有好的嵌入式项目需要用到&#xff0c;哪这里就简单给大家展示一下吧。 原始包装大概就是这样子了垃&#xff0c;有两个存储芯片和一个简单的转接器&#xff0c;测试的时候可以把芯片焊接到转接器…

如何安装mysl驱动程序jar包

简介&#xff08;为什么要安装mysql驱动jar包&#xff09; MySQL 驱动程序&#xff08;通常以 JAR 文件的形式提供&#xff09;用于在 Java 应用程序中连接和与 MySQL 数据库进行交互。这些驱动程序提供了一组 API&#xff0c;使 Java 应用程序能够执行诸如查询、插入、更新和…

【月报】​Aavegotchi 开发更新 |2024 年 4 月版,多款游戏上新玩法

朋友们好&#xff01; 春天来了&#xff0c;我们热情洋溢的团队很高兴能为 Gotchiverse 带来一堆新鲜的更新和丰富的功能。让我们一起来看看这次开发更新带来了什么&#xff1a; Gotchichain 选择定居基地 精神力量竞技场获得了 EBIC 更新 高奇守护者通过全新的进阶系统提升…

C# APS.NET CORE 6.0 WebApi在IIS部署报错

今天尝试着把基于 APS.NET CORE6.0开发的webAPI程序部署到IIS中&#xff0c;当打开网站地址时报错&#xff0c;无法打开&#xff0c;于是查找资料最终进行了解决。 打开 IIS →模块 查看列表中是否存在 AspNetCoreModuleV2&#xff0c;如下&#xff1a; 对应的应用池需要选择“…

海外云服务对比: AWS、GCP、Azure 与 DigitalOcean

云计算市场持续增长&#xff0c;预计到2030年将达到 2432.87 亿美元。在这个庞大的市场中&#xff0c;三家云服务提供商——亚马逊&#xff08;AWS&#xff09;、谷歌云平台&#xff08;GCP&#xff09;和微软Azure——共占云市场份额的64%。当用户选择云服务提供商来托管他们的…