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. EPSS 解读:与 CVSS 相比,孰美?

    通用漏洞评分系统(CVSS)是当前应用最频繁的评分系统以评估安全漏洞的严重性.但是,由于该系统在评估漏洞和优先级排序方面存在不足而遭受批评.因此,有部分专业人士呼吁使用漏洞利用预测评分系统(EPSS) ...

  2. async.js 版本兼容问题 async.filter举例

    async3.x 和async2.6.1 版本下 const files = ['dir1/file1.txt','dir2/file3.txt','dir3/file6.txt']; // Usin ...

  3. python 之用户自定义函数

    什么是函数? 函数无非就是将代码块进行封装,想用的时候拿来用,减少代码量,提高效率. 函数的定义 定义一个函数需要: 1.def关键字,def 后面空一格输入函数名称,函数命名时尽量简短,且具有意义, ...

  4. UVA 673 Paretheses Balance

    原题Vjudge 题目大意 怼给你一堆括号,判断是否合法 有三条规则 (1)空串合法 (2)如果\(A和B\)都合法,则\(AB\)合法(例如:\(()和[]\)都合法,则\(()[]\)合法) (3 ...

  5. [深度学习]DEEP LEARNING(深度学习)学习笔记整理

    转载于博客http://blog.csdn.net/zouxy09 一.概述 Artificial Intelligence,也就是人工智能,就像长生不老和星际漫游一样,是人类最美好的梦想之中的一个. ...

  6. Redis-01 常用命令

    创建和获取 key 命令 说明 例子 set 创建一个名为 key 值为 value 键值对 set views 10 get 获取名为 key 的值,存在返回值,不存在返回 nil get view ...

  7. Spark详解(07-1) - SparkStreaming案例实操

    Spark详解(07-1) - SparkStreaming案例实操 环境准备 pom文件 <dependencies>     <dependency>         &l ...

  8. ABC193F Engines

    简要题意 给出 \(n\) 个向量,求其子集的和的最大模长. \(1 \leq n \leq 100\) 思路 先说结论:选出的几个向量,一定是极角排序后的某一段(环形)区间. 这个不难感性理解,比如 ...

  9. 如何理解scanf(“%d %d”,a,b)==2和scanf(“%d”,a)=1【摘抄笔记ψ(._. )>】

    scanf 函数有一个返回值,0表示接受输入失败,1表示接受输入成功. while(scanf("%d",&x)==1) 的意思就是: 当接收输入变量x的值成功的时候,继续 ...

  10. 1.5万字长文:从 C# 入门 Kafka

    目录 1, 搭建 Kafka 环境 安装 docker-compose 单节点 Kafka 的部署 Kafka 集群的部署 2, Kafka 概念 基本概念 关于 Kafka 脚本工具 主题管理 使用 ...