讲师中心 微信公众号
AI工具推荐 视频效率加速

c# 如何防止ASP.NET Core应用在启动时并发执行初始化代码

阿枫姑娘_1080

阿枫姑娘_1080

发布时间:2026-02-12 16:00:43

|

384人浏览过

|

来源于php中文网

原创

应使用 Lazy<T> 或带锁工厂实现线程安全的单例初始化:Lazy 配合 ExecutionAndPublication 模式确保构造函数仅执行一次;依赖 DI 时需用私有静态锁保护工厂委托;IHostedService 中须用 AsyncLazy<T> 避免 StartAsync 并发风险。

c# 如何防止asp.net core应用在启动时并发执行初始化代码

使用 Lazy<t></t> 包裹单例初始化逻辑

ASP.NET Core 默认使用多线程启动(尤其在 Kestrel + 多核 CPU 下),Program.cs 或 Startup.ConfigureServices 中的静态初始化、静态字段赋值、或单例服务的构造函数,都可能被多个线程同时触发。直接在构造函数里写耗时初始化(如加载配置、连接数据库、预热缓存)会导致重复执行甚至竞态错误。

Lazy<t></t> 是 .NET 原生线程安全的延迟初始化机制,配合 LazyThreadSafetyMode.ExecutionAndPublication 可确保仅一次执行且结果对所有线程可见。

public class ExpensiveInitializer
{
    private static readonly Lazy<ExpensiveInitializer> _instance = 
        new Lazy<ExpensiveInitializer>(() => new ExpensiveInitializer(), 
            LazyThreadSafetyMode.ExecutionAndPublication);

    public static ExpensiveInitializer Instance => _instance.Value;

    private ExpensiveInitializer()
    {
        // 这段代码只会被执行一次,即使多个线程同时首次访问 Instance
        Console.WriteLine("Initializing...");
        Thread.Sleep(1000); // 模拟耗时操作
    }
}
  • 必须显式指定 LazyThreadSafetyMode.ExecutionAndPublication,否则默认模式在某些 .NET 版本下可能不保证构造函数只调一次
  • 不要把 Lazy<T> 实例放在非静态字段中——那会失去“全局唯一初始化”的意义
  • 如果初始化过程可能抛异常,Lazy<T> 会缓存异常,后续访问直接重抛;需自行捕获并处理或改用 AsyncLazy<T>

在 IServiceProvider 中注册时使用工厂委托 + 锁保护

当初始化逻辑依赖 DI 容器(比如需要 IConfiguration 或 ILogger),就不能用纯静态 Lazy<T>,而应在 ConfigureServices 中用工厂方法注册,并手动加锁控制首次执行。

注意:不能用 lock 锁住 this 或类型对象(如 typeof(MyService)),因为此时 IServiceCollection 还未构建完成,锁对象可能不唯一;推荐用私有静态对象。

C#
C#

"避开 C# 常见错误——空陷阱、异步陷阱、LINQ 陷阱及资源泄漏。"

下载
private static readonly object _initLock = new object();
private static bool _initialized = false;

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMyService>(sp =>
    {
        lock (_initLock)
        {
            if (!_initialized)
            {
                var config = sp.GetRequiredService<IConfiguration>();
                var logger = sp.GetRequiredService<ILogger<MyService>>();
                // 执行初始化:读配置、建连接池、预热等
                logger.LogInformation("Running one-time init...");
                InitializeOnce(config);
                _initialized = true;
            }
        }
        return new MyService();
    });
}
  • 该方式适用于初始化必须依赖 DI 服务的场景,但要注意:锁会阻塞其他服务注册线程,应尽量缩短临界区(只包真正需要同步的部分)
  • 避免在锁内调用异步方法或等待 I/O(如 await),否则会阻塞整个容器构建流程
  • 如果初始化本身是异步的(如 await LoadCacheAsync()),需改用 AsyncLazy<T> 或信号量(SemaphoreSlim)+ 异步锁模式

警惕 IHostedService.StartAsync 的并发调用风险

很多开发者误以为把初始化移到 IHostedService 就天然串行,其实 ASP.NET Core 会并发调用所有已注册的 IHostedService.StartAsync ——除非你显式控制顺序或同步。

常见错误:多个 IHostedService 都尝试初始化同一资源(如 Redis 连接、内存缓存项),导致重复连接或覆盖。

  • 若必须用 IHostedService,应在实现类内部用 Lazy<Task> 或 AsyncLazy<T> 包装初始化逻辑
  • 不要在 StartAsync 中直接写 if (!_inited) { DoInit(); _inited = true; } —— 多个实例可能同时通过判断,造成竞态
  • 更稳妥的做法是:让初始化逻辑集中在一个专用的 IHostedService 中,并通过 IServiceProvider 注入它所初始化的资源,其他服务只消费不初始化

为什么不用 ConcurrentDictionary.GetOrAdd?

有人尝试用 ConcurrentDictionary<string, object>.GetOrAdd("init", _ => { ... }) 实现一次性初始化,这看似可行,但存在隐患:

  • GetOrAdd 的 valueFactory 可能被多次调用(虽然只有一次胜出),若初始化逻辑有副作用(如发 HTTP 请求、写日志、修改静态状态),就会意外触发多次
  • 无法区分“初始化成功”和“初始化失败后重试”,异常会被吞掉或重复抛出
  • 不如 Lazy<T> 语义清晰、行为确定,.NET 团队也明确推荐 Lazy<T> 用于一次性初始化场景

真正需要防止并发初始化的地方,核心就两条:用对机制(Lazy<T> 或带锁工厂),并且把“是否已完成”的状态绑定到线程安全的存储上——而不是靠条件判断或字典试探。

热门AI工具

更多
UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6539

2023.08.02

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4298

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

1189

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

403

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

559

2026.01.21

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

389

2026.02.06

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

233

2026.03.16

Java 多线程与并发编程实战
Java 多线程与并发编程实战

深入讲解 Java 并发编程体系,涵盖 Thread / Runnable / Callable 线程创建方式、线程生命周期与状态转换、synchronized 同步锁与 Lock/ReentrantLock 显式锁、volatile 可见性保证、ThreadPool 线程池配置与调优(核心参数/拒绝策略)、JUC 并发工具类(CountDownLatch/CyclicBarrier/Semaphore/ConcurrentHashMa

349

2026.04.17

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

0

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn