Lazy可以提供多线程环境下的安全保障,但是用不好也是会跳到坑里。

我这里使用Lazy<t>(Func<T>)来创建一个Lazy实例,然后在需要的地方访问它的Value属性,它可以保证在多线程环境下Func<T>仅执行一次,这看起来十分的美好:需要的时候执行,并且仅执行一次,再翻译下就是延迟加载,线程安全,资源消耗少。

问题

但是程序运行一段时间后出现了诡异的情况:出现一次异常后,程序不能自动恢复,一直抛出异常,直到程序重启,而出现异常的地方就在Func<T>中。

所有的好冥冥之中都是有代价的,查阅官方文档,发现Lazy会缓存异常。

https://docs.microsoft.com/en-us/dotnet/api/system.lazy-1.-ctor?view=net-5.0#System_Lazy_1__ctor_System_Func__0__

Lazy<T>(Func<T>) 等同于 Lazy<T>(Func<T>, true) 或者 Lazy<T>(Func<T>,LazyThreadSafetyMode.ExecutionAndPublication),后边这两个构造函数的第二个参数的意思是在多线程环境下,委托只执行1次,使用这次的执行结果作为Lazy的值,同时如果委托中发生任何异常,都会被缓存下来。

官方还提供了一个例子可以验证异常缓存的问题,粘贴到这里:

using System;
using System.Threading; class Program
{
static Lazy<LargeObject> lazyLargeObject = null; static LargeObject InitLargeObject()
{
return new LargeObject();
} static void Main()
{
// The lazy initializer is created here. LargeObject is not created until the
// ThreadProc method executes.
lazyLargeObject = new Lazy<LargeObject>(InitLargeObject); // The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true);
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, LazyThreadSafetyMode.ExecutionAndPublication); Console.WriteLine(
"\r\nLargeObject is not created until you access the Value property of the lazy" +
"\r\ninitializer. Press Enter to create LargeObject.");
Console.ReadLine(); // Create and start 3 threads, each of which tries to use LargeObject.
Thread[] threads = { new Thread(ThreadProc), new Thread(ThreadProc), new Thread(ThreadProc) };
foreach (Thread t in threads)
{
t.Start();
} // Wait for all 3 threads to finish. (The order doesn't matter.)
foreach (Thread t in threads)
{
t.Join();
} Console.WriteLine("\r\nPress Enter to end the program");
Console.ReadLine();
} static void ThreadProc(object state)
{
try
{
LargeObject large = lazyLargeObject.Value; // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
// object after creation. You must lock the object before accessing it,
// unless the type is thread safe. (LargeObject is not thread safe.)
lock(large)
{
large.Data[0] = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Initialized by thread {0}; last used by thread {1}.",
large.InitializedBy, large.Data[0]);
}
}
catch (ApplicationException aex)
{
Console.WriteLine("Exception: {0}", aex.Message);
}
}
} class LargeObject
{
int initBy = 0;
public int InitializedBy { get { return initBy; } } static int instanceCount = 0;
public LargeObject()
{
if (1 == Interlocked.Increment(ref instanceCount))
{
throw new ApplicationException("Throw only ONCE.");
} initBy = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
}
public long[] Data = new long[100000000];
} /* This example produces output similar to the following: LargeObject is not created until you access the Value property of the lazy
initializer. Press Enter to create LargeObject. Exception: Throw only ONCE.
Exception: Throw only ONCE.
Exception: Throw only ONCE. Press Enter to end the program
*/

解决方案

在提出解决办法前,需要想一下,为什么会缓存异常?

因为要保证多线程环境下只执行一次,如果异常了还允许再次执行,就不能保证只执行一次了,而有些程序多次执行是不可行的。

来看几个解决方案:

1、不使用Lazy,自己加锁处理。

出现问题的程序中Lazy内部也是用了锁。

部分情况下可以用双检锁或则带升级的读写锁,以提高读的性能。

如果发生异常,可以抛到上层,并且再次获取时会重试执行。

2、使用Value时如果有异常,则重新给Lazy赋值。

不过这可能又要求赋值时线程安全。

3、如果经过评估可以多次创建Value,则可以更改线程安全模式为:LazyThreadSafetyMode.PublicationOnly

在这种模式下:多线程时每个线程都会创建,但是只使用第一个创建的,同时不缓存异常,异常发生后再次获取时会重新执行。

哪个适合自己,还需自己选择。

Lazy(Func<T>)的异常缓存问题的更多相关文章

  1. Hibernate(四)——缓存策略+lazy

    Hibernate作为和数据库数据打交道的框架,自然会设计到操作数据的效率问题,而对于一些频繁操作的数据,缓存策略就是提高其性能一种重要手段,而Hibernate框架是支持缓存的,而且支持一级和二级两 ...

  2. Hibernate框架(四)缓存策略+lazy

    Hibernate作为和数据库数据打交道的框架,自然会设计到操作数据的效率问题,而对于一些频繁操作的数据,缓存策略就是提高其性能一种重要手段,而Hibernate框架是支持缓存的,而且支持一级和二级两 ...

  3. [你必须知道的.NET]第三十三回,深入.NET 4.0之,Lazy<T>点滴

    发布日期:2009.10.29 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 对象的创建方式,始终代表了软件工业的生产力方向,代表了先进软件技 ...

  4. 01-08-05【Nhibernate (版本3.3.1.4000) 出入江湖】NHibernate二级缓存:第三方MemCache缓存

    一.准备工作 [1]根据操作系统(位数)选择下载相应版本的MemCache, MemCache的下载和安装,参看: http://www.cnblogs.com/easy5weikai/p/37606 ...

  5. Lazy<T>

    Lazy<T> 对象的创建方式,始终代表了软件工业的生产力方向,代表了先进软件技术发展的方向,也代表了广大程序开发者的集体智慧.以new的方式创建,通过工厂方法,利用IoC容器,都以不同的 ...

  6. 第23课 可变参数模板(4)_Optional和Lazy类的实现

    1. optional类的实现 (1)optional的功能 ①optional<T>的内部存储空间可能存储了T类型的值,也可能没有.只有当optional被T初始化之后,这个option ...

  7. C#基础知识回顾---你不知道的Lazy<T>

    对象的创建方式,始终代表了软件工业的生产力方向,代表了先进软件技术发展的方向,也代表了广大程序开发者的集体智慧.以new的方式创建,通过工厂方法,利用IoC容器,都以不同的方式实现了活生生实例成员的创 ...

  8. C# Lazy Loading

    前言 按需加载对象延迟加载实际是推迟进行创建对象,直到对其调用后才进行创建初始化,延迟(懒加载)的好处是提高系统性能,避免不必要的计算以及不必要的资源浪费. 常规有这些情况: 对象创建成本高且程序可能 ...

  9. ASP.NET中的缓存机制

    ASP.NET 提供一个功能完整的缓存引擎,页面可使用该引擎通过 HTTP 请求存储和检索任意对象.缓存的生存期与应用程序的生存期相同,也就是说,当应用程序重新启动时,将重新创建缓存. 将数据添加到缓 ...

  10. Lazy evaluation

    是一段源码,关于Lazy evaluation的,看了很久才懂,记录一下 一,lazy方法返回的比较复杂,一层一层将其剥开. wraps(func)跳转到curry(update_wrapper, f ...

随机推荐

  1. AspectCore和MSDI 实现Name注册以及解析对象

    AspectCore 在注册服务这块比较简单,默认是无法根据Name去注册和解析对象,这边做一下这块的扩展 大致原理是根据自定义Name去生成对应的动态类型,然后使用委托或者对象的方式,进行注册 ti ...

  2. 快速上手Linux核心命令(十):Linux安装软件

    目录 前言 rpm rpm包管理器 yum 自动化RPM包管理工具 前言 这期呢主要说一说Linux中包软件管理相关命令,这一期的命令虽然只有两个.但 软件包的安装和卸载都是我们平常最常用的,需要熟练 ...

  3. 如何在SpringBoot项目中兼容Jersey和SpringMVC框架?

    文章目录 Jersey框架介绍 常用的注解: SpringBoot中SpringMVC兼容Jersey 整合Jersey REST(Representational State Transfer)表象 ...

  4. Unity中实现字段/枚举编辑器中显示中文(中文枚举、中文标签)

    在unity开发编辑器相关经常会碰到定义的字段显示在Inspector是中文,枚举也经常碰到显示的是字段定义时候的英文,程序还好,但是如果编辑器交给策划编辑,策划的英文水平不可保证,会很头大,所以还是 ...

  5. MySQL对时间戳的格式化

    时间转时间戳 select unix_timestamp('2022-02-22 22:22:22'); 时间戳转时间 select from_unixtime(1645539742); 格式化 SE ...

  6. 2020-12-26:mysql中,表person有字段id、name、age、sex,id是主键,name是普通索引,age和sex没有索引。select * from person where id=1 and name='james' and age=1 and sex=0。请问这条语句有几次回表?

    2020-12-26:mysql中,表person有字段id.name.age.sex,id是主键,name是普通索引,age和sex没有索引.select * from person where i ...

  7. OCR -- 文本检测

    目录 目标检测: 文本检测: 检测难点: 检测方法: 基于回归的文本检测 水平文本检测 任意角度文本检测 弯曲文本检测 基于分割的文本检测 代码示例 可视化文本检测预测 DB文本检测模型构建 back ...

  8. 牧云 • 主机管理助手|正式开放应用市场,梦幻联动雷池WAF等多款开源软件

    0x00 前言 上个月,我司长亭开源了雷池WAF,不到三天就吸引了超过上千个师傅使用,几个交流群里,师傅们讨论的热火朝天,其中两个话题引起了我们牧云 • 主机管理助手 ( Collie ) 团队的关注 ...

  9. Python连接es笔记二之查询方式汇总

    本文首发于公众号:Hunter后端 原文链接:Python连接es笔记二之查询方式汇总 上一节除了介绍使用 Python 连接 es,还有最简单的 query() 方法,这一节介绍一下几种其他的查询方 ...

  10. bugku_MagicImageViewer

    CTF 安卓逆向 MagicImageViewer--png结构+算法 很少做安卓逆向的题目,在此记录一下 先用模拟器看一下 嗯,没啥提示. jeb打开 关键部分 if(s.length() == 1 ...