Microsoft.Extensions.DependencyInjection中的Transient依赖注入关系,使用不当会造成内存泄漏
Microsoft.Extensions.DependencyInjection中(下面简称DI)的Transient依赖注入关系,表示每次DI获取一个全新的注入对象。但是使用Transient依赖注入关系时,最好要配合IServiceScope来一起使用,因为通过Transient依赖注入关系创建的对象,都会被创建它的ServiceProvider对象内部引用,这样会造成注入对象无法被GC及时回收,造成内存泄漏,只有当调用ServiceProvider对象的Dispose方法后,ServiceProvider才会解除其内部对注入对象的引用,之后这些注入对象才能被GC回收。
我们新建一个.NET Core控制台项目,然后假设我们有接口IPeople和实现类People,他们之间的依赖注入关系是Transient。
现在,如果我们的代码中有一个for循环,它会循环1000次,每一次都会从DI中获取一个IPeople对象实例,由于接口IPeople和类People是Transient关系,所以每次DI都会创建一个新的People对象实例。但是我们只需要在每次循环中,调用People类的DoSomething方法做一些事情后,就不需要创建的People对象了,也就是说我们希望每次循环结束后,GC都能尽量回收在循环中创建的People对象实例。
所以我们下了下面的代码:
using Microsoft.Extensions.DependencyInjection;
using System; namespace NetCoreDITransientInScope
{
interface IPeople
{
void DoSomething();
} class People : IPeople
{
public void DoSomething()
{
Console.WriteLine("DoSomething is running");
}
} class Program
{ static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IPeople, People>();//注册接口IPeople和类People的关系为Transient using (ServiceProvider rootServiceProvider = services.BuildServiceProvider())
{
//执行1000次循环,每一次循环创建一个People对象实例,在rootServiceProvider调用Dispose方法前,创建的1000个People对象实例都不会被GC回收
for (int i = 0; i < 1000; i++)
{
IPeople people = rootServiceProvider.GetService<IPeople>();
people.DoSomething(); //在每次循环结束后,创建的People对象实例无法被GC回收,因为在rootServiceProvider的内部对所有创建的Transient对象都保持了引用,除非调用rootServiceProvider的Dispose方法,否则在每次循环中创建的People对象实例都无法被GC回收
}
} Console.WriteLine("Press any key to end...");
Console.ReadKey();
}
}
}
从上面代码的注释中,我们可以看到,实际上每一次for循环执行完后,GC并不能立即回收在循环中创建的People对象实例,原因是ServiceProvider对象rootServiceProvider的内部引用了由DI创建的所有People对象实例,除非调用rootServiceProvider的Dispose方法(也就是在上面using代码块最后),否则所有的People对象实例都无法被GC回收。设想一下,如果将上面的for循环改为一个死循环(对于有些后台服务程序而言,的确需要死循环),那么DI会创建大量的People对象实例无法被GC及时回收,造成内存泄漏。
所以正确使用Transient依赖注入关系的方法应该是,配合IServiceScope对象来使用,我们将上面的代码改为如下:
using Microsoft.Extensions.DependencyInjection;
using System; namespace NetCoreDITransientInScope
{
interface IPeople
{
void DoSomething();
} class People : IPeople
{
public void DoSomething()
{
Console.WriteLine("DoSomething is running");
}
} class Program
{ static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IPeople, People>();//注册接口IPeople和类People的关系为Transient using (ServiceProvider rootServiceProvider = services.BuildServiceProvider())
{
//执行1000次循环,每一次循环创建一个People对象实例
for (int i = 0; i < 1000; i++)
{
//在每一次循环中创建一个IServiceScope对象serviceScope,然后使用serviceScope的ServiceProvider创建People对象实例,这样在rootServiceProvider中并没有对People对象实例的引用,只有每次循环中创建的serviceScope中的ServiceProvider保持了People对象实例的引用,这样在每次循环中当调用serviceScope的Dispose方法后,单次循环中创建的People对象实例就可以被GC回收了,而不是等到rootServiceProvider调用Dispose方法后,才能被GC回收
using (IServiceScope serviceScope = rootServiceProvider.CreateScope())
{
IPeople people = serviceScope.ServiceProvider.GetService<IPeople>();
people.DoSomething();
}
}
} Console.WriteLine("Press any key to end...");
Console.ReadKey();
}
}
}
从上面代码中,我们可以看到,由于现在在每次for循环中,是由一个独立的IServiceScope对象serviceScope的ServiceProvider,来创建People对象实例,所以在for循环外面的ServiceProvider对象rootServiceProvider,其并没有内部引用由DI创建的People对象实例。而在每次for循环中,我们都调用了serviceScope的Dispose方法(也就是在上面第二个using代码块最后),这样每次循环结束后,就没有任何代码引用循环内的People对象实例了,GC就可以及时回收由DI创建的People对象实例。
在使用Microsoft.Extensions.DependencyInjection的Transient依赖注入关系时,一定要注意本文所述的内存泄漏问题,这个问题可能很多才开始接触Microsoft.Extensions.DependencyInjection的开发人员不会注意到,但是它会严重影响你的程序性能和稳定性。可以参考下面两篇帖子中发帖人提出的问题:
IServiceProvider garbage collection / disposal
When are .NET Core dependency injected instances disposed?
也可以参考在GitHub上,微软官方对这个问题的讨论:
Revisit tracking transient services for disposal
Microsoft.Extensions.DependencyInjection中的Transient依赖注入关系,使用不当会造成内存泄漏的更多相关文章
- Microsoft.Extensions.DependencyInjection 之三:展开测试
目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...
- Microsoft.Extensions.DependencyInjection 之三:反射可以一战(附源代码)
目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...
- 使用 Microsoft.Extensions.DependencyInjection 进行依赖注入
没有 Autofac DryIoc Grace LightInject Lamar Stashbox Unity Ninject 的日子,才是好日子~~~~~~~~~~ Using .NET Core ...
- 使用诊断工具观察 Microsoft.Extensions.DependencyInjection 2.x 版本的内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
- Microsoft.Extensions.DependencyInjection 之二:使用诊断工具观察内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
- 【17MKH】我在框架中对.Net依赖注入的扩展
说明 依赖注入(DI)是控制反转(IoC)的一种技术实现,它应该算是.Net中最核心,也是最基本的一个功能.但是官方只是实现了基本的功能和扩展方法,而我呢,在自己的框架 https://github. ...
- DotNetCore跨平台~一起聊聊Microsoft.Extensions.DependencyInjection
写这篇文章的心情:激动 Microsoft.Extensions.DependencyInjection在github上同样是开源的,它在dotnetcore里被广泛的使用,比起之前的autofac, ...
- 解析 Microsoft.Extensions.DependencyInjection 2.x 版本实现
项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍然适用. 先说结论 ...
- Microsoft.Extensions.DependencyInjection 之一:解析实现
[TOC] 前言 项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍 ...
随机推荐
- 透过 Cucumber 学习 BDD
在需求的开发过程中,最令人困惑的地方就在于需求模糊.需求是解决业务的问题,那么验收的方式应该是由业务方提出,但是往往业务方(可能是产品经理,也可能是直接是客户)只能给出比较模糊的一个验收标准,而程序却 ...
- VS2017报错:未识别符vector
解决办法:添加头文件#include<vector>,添加命名空间 using namespace std.
- kvm虚拟化存储管理(3)
一.KVM 存储虚拟化介绍 KVM 的存储虚拟化是通过存储池(Storage Pool)和卷(Volume)来管理的. Storage Pool 是宿主机上可以看到的一片存储空间,可以是多种型: Vo ...
- 工作10年后,再看String s = new String("xyz") 创建了几个对象?
这个问题相信每个学习java的同学都不陌生,作为一个经典的面试题,到现在工作这么多年了我真是认为挺操蛋的一个问题,在网上到现在你仍然可以看见很多讨论这个问题的人,其中不乏工作很多年的人都有争论,我认为 ...
- 番外篇 - Linux环境准备
这是一个比较早的系列,最近发现一直没有更新... asp.net core跨平台,所以我们首先需要一个linux来验证,所以第一篇就是准备我们的环境 .netcore尝试在centos6. ...
- MySQL分区 (分区介绍与实际使用)
分区介绍: 一.什么是分区? 所谓分区,就是将一个表分成多个区块进行操作和保存,从而降低每次操作的数据,提高性能.而对于应用来说则是透明的,从逻辑上看只有一张表,但在物理上这个表可能是由多个物理分区组 ...
- Spring学习(三)--Spring的IOC
1.BeanFactory和FactoryBean BeanFactory是一个接口类,定义了IOC容器最基本的形式,提供了IOC容器所应该遵守的基本服务契约. FactoryBean是一个能产生或者 ...
- ThinkPHP 5 生命周期
前段时间用TP5开发了一个小程序,就熟悉了一下TP5.TP5是TP框架最新的一个版本,与以前的3还是有很大的区别,有人说和laravel比较靠近,其实也还好,每个人都有自己不同的看法,只要是选择一个自 ...
- 065 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参无返回值方法
065 01 Android 零基础入门 01 Java基础语法 08 Java方法 03 带参无返回值方法 本文知识点:带参无返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...
- P5858 「SWTR-03」Golden Sword
题面: Link 题面有点长,不想粘了,QAQ. 题解: 一句话题意,你有 \(n\) 件物品需要依次放进去,每个物品放进去之后会得到一定的权值,为当前锅炉里面的物品的数量乘以 \(a_i\) 每次在 ...