lazy默认线程安全,但需显式指定lazythreadsafetymode;value首次访问才初始化并缓存结果或异常,后续访问直接返回;工厂函数内须加try/catch记录上下文,避免异常掩盖根源。

LazyLazyThreadSafetyMode,否则可能重复初始化、卡死线程,甚至掩盖真实异常。
Lazy.Value 第一次访问就崩了,怎么定位初始化失败点?
LazyValue 访问失败后,后续每次读都直接 rethrow 同一个异常,不会重试。这导致你看到的错误日志永远是“同一条”,但根本不知道初始化逻辑里哪一行出了问题。
- 在工厂函数内加
try/catch,至少记录完整堆栈和上下文(比如配置名、连接字符串) - 避免把整个构造逻辑塞进一行:不要写
new Lazy<foo>(() => new Foo(Config.Load()))</foo>,而是拆成独立方法,方便断点和日志 - 用
IsValueCreated判断是否已触发初始化,辅助排查是不是还没走到Value就被其他逻辑提前破坏了状态
多线程环境下 Lazy 初始化重复执行?检查线程安全模式
默认的 LazyThreadSafetyMode.ExecutionAndPublication 确保只初始化一次,但如果手动指定为 LazyThreadSafetyMode.None,又在 ASP.NET Core 请求线程或 Task.Run 中并发访问 Value,就会创建多个实例——这不是 bug,是设计行为。
- 确认是否真需要禁用线程安全:只有单线程控制流(如 WinForms 主线程初始化 UI 资源)才考虑
None -
PublicationOnly模式允许并发初始化,但只保留第一个成功结果;适合初始化逻辑幂等、且失败成本低的场景(比如加载只读配置片段) - 别依赖“默认值”隐式行为:显式写出
LazyThreadSafetyMode.ExecutionAndPublication,避免团队成员误改
为什么 private readonly Lazy 字段不能写成属性 getter?
写成 public Lazy<t> Items => new Lazy<t>(LoadItems);</t></t> 是典型误用:每次调用 getter 都新建一个 Lazy<t></t> 实例,彻底失去延迟加载和单例意义。
- 必须声明为
private readonly字段,生命周期绑定到宿主对象 - 如果初始化依赖
this(比如() => new Service(_config)),确保字段初始化顺序正确——_config必须在Lazy<t></t>构造前已赋值 - 值类型(
int、DateTime)别套Lazy<t></t>:委托调用 + 同步开销远超收益
Async 场景下不能直接用 Lazy,得换包装方式
Lazy<t></t> 的工厂函数类型是 Func<t></t>,不支持 async;强行 await 会导致同步阻塞,尤其在 ASP.NET 同步上下文里极易死锁。
- 正确做法是用
Lazy<task>></task>:工厂返回Task<t></t>,Value返回的是同一个Task实例,不会重复发起请求 - 示例:
private readonly Lazy<task>> _data = new Lazy<task>>(LoadDataAsync);</task></task>,再通过await _data.Value消费 - 别用
Task.Run(() => new ExpensiveObject())包裹同步构造——这没解决延迟问题,只是把阻塞挪到了线程池
最易被忽略的一点:Lazy











