惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
InfoQ
C
CERT Recently Published Vulnerability Notes
The Last Watchdog
The Last Watchdog
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
T
Tenable Blog
博客园 - 三生石上(FineUI控件)
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
Jina AI
Jina AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
博客园_首页
F
Fortinet All Blogs
博客园 - Franky
Latest news
Latest news
Last Week in AI
Last Week in AI
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
D
Docker
Project Zero
Project Zero
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
F
Full Disclosure
D
DataBreaches.Net
Google DeepMind News
Google DeepMind News
Cisco Talos Blog
Cisco Talos Blog
Y
Y Combinator Blog
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Help Net Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
PCI Perspectives
PCI Perspectives
Cloudbric
Cloudbric
V
Visual Studio Blog
Recorded Future
Recorded Future
人人都是产品经理
人人都是产品经理

博客园 - Agile.Zhou

AgileAI - 一个新的 .NET AI 库 并发,并行与异步 为什么说 IO 操作异步才有意义 自定义 Visual Studio 主题 使用 Azure AI Foundry 微调模型 SK + Neo4j 实现简单问答系统 AgileConfig-1.11.0 发布:增强的权限管理 LongRunningTask-正确用法 如何正确实现一个 BackgroundService Dynamic adaptation to application sizes (DATAS) GC 策略 使用 AutoGen Studio 打造你的私有团队 在 Aspire 项目下使用 AgileConfig 使用 SK 进行向量操作 本地部署 DeepSeek Janus Pro 文生图大模型 Kernel Memory 让 SK 记住更多内容 在 Development 环境下依赖注入的行为可能有所不同 使用 SK Plugin 给 LLM 添加能力 使用 SemanticKernel 对接 Ollma 在 Github Action 管道内集成 Code Coverage Report
.NET 依赖注入中的 Captive Dependency
Agile.Zhou · 2025-01-10 · via 博客园 - Agile.Zhou

大家好,上一篇我们分析了 .NET 依赖注入的默认行为,其实呢还没完全讲完。今天我先给大家出一道题:

    public interface IDbContext
    {

    }

    public class SqlServerDbContext : IDbContext
    {

    }

    public class LongTermSerive : BackgroundService
    {
        private readonly IDbContext _context;

        public LongTermSerive(IDbContext context)
        {
            _context = context;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            return Task.CompletedTask;
        }
    }

builder.Services.AddScoped<IDbContext, SqlServerDbContext>();

builder.Services.AddHostedService<LongTermSerive>();

请问以上服务的注册有没有问题?
熟悉 .NET 的同学很快就会说:这当然有问题IDbContextScope 生命周期,LongTermSerive 因为注册成了 HostedService 所以实际上它是 Singleton 生命周期。Singleton 不能持有 Scope 生命周期的服务。说的更通用一点的话就是:生命周期长的服务无法依赖生命周期比它的服务。
真的是这样吗???
以上回答只说对了一半。这时候肯定马上会有同学跳出来说,“这怎么会不对呢?我刚刚都试过了,VS直接报错了”。

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: DevelopmentTest.LongTermSerive': Cannot consume scoped service 'DevelopmentTest.IDbContext' from singleton 

不要着急让我们继续分析下去。

Captive Dependency

首先让我们澄清一个概念。像以上这种情况:当生命周期长(Singleton)的服务持有生命周期短(Scope)的服务的时候我们叫做 "Captive Dependency"(Transient不在讨论范围内)。

不知道怎么翻译成中文比较合适。微软的文档上翻译作"捕获依赖",个人认为不太恰当。

"Captive Dependency" 会带来什么问题?

  1. 生命周期短的服务会被 DI 容器及时释放,比如调用了 Dispose 方法,导致后续的操作失败。
  2. 非线程安全。Singleton 的对象很容易被多个线程共享,但 Scope 的话大多数情况都是非线程安全的。比如上面的 DbContext,当在线程内共享,发生并发操作的时候程序是无法保证正确运行的。

.NET DI 支持 Captive Dependency 吗?

当我们了解这个概念后,上面的问题可以转换成 " .NET DI 支持 Captive Dependency 吗?"。

根据上一次我们的文章的内容,我们知道 .NET DI 的行为是跟所在的环境有关系的。所以讨论这个问题我们还是要分开来看待:

  • Development 环境下,.NET DI 会在构建 ServiceProvider 的时候去校验服务的依赖关系。这个时候就会像上面提到的一样,直接报错。
  • 非 Development 环境下在构建 ServiceProvider 的时候不会校验服务间的依赖关系,程序有可能正确运行。为什么说是有可能呢?因为这个完全取决与你的代码是怎么写的。也许你短生命周期的服务在某些场景下正巧可以工作,又或者正巧不能工作。但是有一点是明确的,就是 Captive Dependency 是危险的。因为当你注册成 Scope 或者 Transient 的时候往往是带了某种暗示。比如 Scope 对象是非线程安全的。显然 Socpe 服务的编写者没有义务去考虑被 Singleton 服务依赖时候的问题。
  • 手动开启 ValidateScopes = true 的时候不管什么环境下都会进行依赖关系的校验,类似 Development 环境下。

总结

现在我们可以作一个总结:
.NET DI 是支持 Captive Dependency 的,但是在 Development 环境下或者手动开启 ValidateScopes = true 的时候它不支持,它会阻止 Captive Dependency。换句话说 .NET DI 在阻止 Captive Dependency 上只做了一半的工作,并不能 100% 确保不发生 Captive Dependency。开发者们在写代码的时候还是要自己注意了,不能完全依赖 .NET 的检测。
关于这个问题,我也在 .NET Runtime 的 Repository 下开了一个 ticket 进行讨论。微软给出的理由是基于性能的考虑,生产环境这个校验默认不开启。其实我个人觉得微软应该不管在什么环境下都默认开启校验,尽可能的避免 Captive Dependency。因为 90% 的项目其实并不在乎这点性能开销。如果你的应用程序真的很在乎性能那么可以手动关闭这个校验,这个时候开发者自己需要完全对这个依赖关系负责。

https://blog.ploeh.dk/2014/06/02/captive-dependency/
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#captive-dependency
https://github.com/dotnet/runtime/discussions/109491