0

0

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

畫卷琴夢

畫卷琴夢

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

|

344人浏览过

|

来源于php中文网

原创

应使用 lazy 或带锁工厂实现线程安全的单例初始化:lazy 配合 executionandpublication 模式确保构造函数仅执行一次;依赖 di 时需用私有静态锁保护工厂委托;ihostedservice 中须用 asynclazy 避免 startasync 并发风险。

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

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

ASP.NET Core 默认使用多线程启动(尤其在 Kestrel + 多核 CPU 下),Program.csStartup.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></t> 实例放在非静态字段中——那会失去“全局唯一初始化”的意义
  • 如果初始化过程可能抛异常,Lazy<t></t> 会缓存异常,后续访问直接重抛;需自行捕获并处理或改用 AsyncLazy<t></t>

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

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

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

Qoder
Qoder

阿里巴巴推出的AI编程工具

下载
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></t> 或信号量(SemaphoreSlim)+ 异步锁模式

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

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

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

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

为什么不用 ConcurrentDictionary.GetOrAdd

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

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

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

708

2023.08.02

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

806

2023.08.22

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

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

653

2023.08.10

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

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

305

2025.12.24

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

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

23

2026.01.21

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

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

24

2026.01.21

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

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

86

2026.02.06

JavaScript中的typeof用法
JavaScript中的typeof用法

在JavaScript中,typeof是一个用来确定给定变量的数据类型的操作符。可以用来确定一个变量是字符串、数字、布尔值、函数、对象或undefined的数据类型。更多关于typeof用法相关文章,详情请看本专题下面的文章,php中文网欢迎大家前来学习。

760

2023.11.23

2026春节习俗大全
2026春节习俗大全

本专题整合了2026春节习俗大全,阅读专题下面的文章了解更多详细内容。

189

2026.02.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 9.2万人学习

C 教程
C 教程

共75课时 | 4.7万人学习

C++教程
C++教程

共115课时 | 17.2万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号